はじめに
おかげさまで、前回の記事は多くの方々からご好評をいただけたようでほっとしております。あちこちのブログなどを拝見していますと、記事をきっかけにして身近な例でERDを書いてみた方も見受けられます。今回も身近な題材でデータベース設計というものを考えていきたいと思います。
合言葉は、「量は質に転化する」。では、今回も張り切って行きましょう。
対象読者
データベース設計の初心者~中級者。
必要な環境
以下のいずれかのRDBMS。
- PostgreSQL 8.0以上
- MySQL 4.0以上
- HSQLDB 1.7.2以上
- MaxDB 7.5以上
- Oracle 10g以上
- SQL Server 2000以上
題材
今回の題材は、近所の図書館にあった書籍の予約用紙(図1)です。子供たちと本を借りに行った際にカウンターに置いてあったので早速利用することにしました。では、データベース設計のはじまりです。
イベントを見出す
まずは核となるテーブルを見出しましょう。そもそもこの紙は何のためのものでしょうか。そう、予約を受け付けるためのものです。つまり「予約」という情報が核になります。ですからまずは「予約」テーブルを用意します(図2)。なお、表記法はIDEF1Xを使います。
さて、予約というのは行為です。もう少し具体的にいうと「~する」という言い方ができます。今回ですと「予約する」という表現が成立します。このような行為の記録となるものを「イベント(出来事)系」のテーブルと呼びます。
イベント系には他にも、出荷であったり入金であったり様々なものがあります。いずれも「~する」という表現が成立します。実はこのイベントの連鎖によって、商売・ビジネスは成り立っています。イベントは何か、ということに注目する癖をつけることで、色々なことに対して目が利くようになります。
イベント系の見出し方として、「~する」ではなくて「~日」という言い方ができるかどうかで判断することもできます。行為は必ず「いつ行ったのか」ということがついて回ります。これを利用して、例えば今回であれば「予約日」と言えるのでイベント系であると判断することも可能です。
リソースを抜き出す
さて、予約するといっても、予約が単独で存在するわけではありません。最低限、何を予約するのかがわからないといけませんし、誰が予約するのかがわからないとお届けもできません。このような観点でもう一度用紙をじっと見ると、「誰が」「何を」ということについての記載があります。「誰が」というのは用紙の上にある「名前」がそれに該当します。また、「何を」というのは中央周辺にある「書名」が該当します。そこでこれらを登録するために、それぞれにテーブルを用意しましょう。今回は「予約者」テーブルと「書籍」テーブルとします(図3)。
恐らく手馴れた方であれば、ここで「図書館のシステムだから」とあれこれ先回りして考えることでしょう。しかし、それは本連載の主旨ではありません。見たままをまずは素直にデータベース設計に落とし込むことを目指しています。残念ながら入手したサンプル以上の(ビジネスルールなどの)情報は得られません。また実際の業務を考えると、はじめての業種・業態であれば先入観が仇となることも往々にしてありえます。まずは先入観を持たずに素直に書くということに慣れてください。
ここで「予約者」テーブルや「書籍」テーブルが現れたわけですが、「~する」という表現は成立するでしょうか。「予約者する」「書籍する」……、いずれも変です。日本語として成立していません。よってこれらはイベント系とは言えないということになります。では、これらは何と呼べばいいでしょうか。これらは商売・ビジネスを行う上で重要な資産であると言えます。そこで、これらを「リソース(資産)系」と呼びます。
リソース系は得てして複雑なデータ構造を持っていたりします。いきなりこれを綺麗に設計しようとすると大変なので、まずはイベント系から洗い出してその後じっくりとリソース系について考えていくほうが、結果として精度の高い設計を実現できます。
また、イベント系において「~する」の代わりに「~日」という言い方ができるかどうかという判断基準を提示しましたが、リソース系の場合「~名」と言えるかどうか、という判断基準も使えます。「予約者名」「書籍名」は意味が通ります。一方で「予約者日」「書籍日」は意味が通りません。ですからリソース系であるといえます。同様に「予約名」というのはちぐはぐな感じを受けます。ですから予約はリソースではなくてイベントだと判断できます。
項目を入れていく
箱を用意したら、それぞれに項目を入れていきましょう。まずはわかりやすいところからということで、「予約者」テーブルから埋めてしまいましょう(図4)。余計なことは考えずに、見た目の通り素直に項目を入れていきます。名前とふりがなと貸出券番号は「予約者」テーブルに入れてしまいましょう。貸出券番号はとりあえず数値型にしておきます。その下の連絡方法ですが、実は意外と難物です。これは後ほど考えることにしましょう。
次にわかりやすいので「書籍」テーブルも埋めてしまいましょう(図5)。出版社については、前回の記事の商品カテゴリのようなものなので、これだけでひとつテーブルを作ってしまっても良いでしょう。また出版年の項目にかっこ書きで金額欄があります。これは書籍の価格と思われます。ですので「価格」という項目も追加しておきます。「この本を何で知りましたか」は実は結構引っ掛け問題のような感じですので、これも後ほど考えましょう。
さて、最後に「予約」テーブルになるのですが、慌てずに順番に項目を入れていきましょう。
まずは、「ご家族に書名をお伝えしてよろしいですか?」という項目です。これは予約者にかかるものでしょうか? 恐らくは毎回の予約ごとに「今回はお伝えしてもよいですか?」と尋ねるものだと考えます。ということは、「予約」テーブルに項目を持たせたほうが素直です。今回は項目名にこのまま転用するのは長すぎる気がしますので、ちょっと偉そうな項目名にして「書名伝達可否」としておきます。申込日は当たり前に入りますね。
次に「受取希望館又はみどり号ステーション」という項目があります。これは補足しておく必要があるのですが、この図書館は市立です。受取希望館というのは市内のどの図書館で受け取るかというのを指定できるということです。また、みどり号というのは移動図書館のことで、バンを改造して本をたくさん積んであちこちに現れます。これらのどこで受け取るかということですから、これもまた予約の一環ということになります。そこで「予約」テーブルに項目を追加することになります。しかし、受取希望館はフリーワードではなく、あらかじめ決まったものから選択することになります。そこで「図書館」テーブルをひとつ追加して、そちらを後ほど予約から参照する形に整えたいと思います(図6)。
意外と難しい連絡方法
さて、先ほど保留しておいた「連絡方法」についてです。これは意外と複雑な情報を読み取ることができます。番号を入れるのはひとつです。そしてその番号の種類が何かということをひとつ決めることになっています。ここで悩むのは、同じ人の場合は毎回同じ連絡方法ということか、ということです。
つまり、予約のたびに切り替えるのであれば、予約の附帯情報になりますが、毎回同じで良いのであれば予約者の付帯情報になります。番号というのは予約者にぶら下がっているものだといえますが、そのうちのどれを「今回の予約の連絡方法」に使うのかは都度切り替わるということであれば、それなりのテーブル構造になります。
そしてもうひとつのポイントは「不要」という選択肢があることです。これは不思議です。予約していながら本が入庫しても連絡してもらう必要がないというのですから。そこでさらにじっと見てみると、そもそも用紙のタイトルが「予約(リクエスト)申込書」となっていることに気付きます。そうです、この用紙はふたつの役割を担っているのです。
ふたつの役割とは、ひとつは「この本を借りたいので、返却されてきたら次に私に貸してくださいね」と予約するための用紙であるということです。そしてもうひとつは「この本が置いてあるといいんじゃないでしょうか」という入庫のお願い、つまり要望(=リクエスト)です。後者であれば確かに別に連絡してくれなくても構わないという選択肢は納得できます。
しかしそうなってくると、そもそも予約者のところは記入がいらないのではないか、ということにも考えが至ります。連絡が不要であれば、連絡先の名前や貸出券番号を知る必要もないからです。明確に「連絡は不要です」と意志表明したということは記録しておくということで、今回は「連絡不要区分」を持たせておきます。これは連絡先をnull
値で代用することでも表現可能ですが、記事としてのわかりやすさから項目として持たせておきます(図7)。
「知った」とは何か?
もうひとつの保留項目として「この本を何で知りましたか」というのがあります。これは実は予約とは別のイベントです。「知った」というのはこれだけでひとつの行為です。誰が何を知ったのかということが必要になります。「知った」というのは書籍だけにぶら下がるわけではありません。同じ本であっても個人ごとにどのように知ったのかは異なります。ですから予約者と書籍の間にもうひとつ「知った」というイベント系のテーブルを置いてあげることにします。
実はここで更に悩むことになります。実は「何によって知ったのか」という手段がここでは問われているのですから、実はテーブル名としては「情報源」とでもしたほうがわかりやすいといえます。しかし、そのようなテーブルを作ると、「予約者」が「書籍」を「情報源」、と並べたときにやはり違和感があります。ここはやはり「予約者」が「書籍」を「知った」のほうがスマートでしょう。もし情報源に関してもっと踏み込んだ管理が必要であれば、改めてこのテーブルは整理するということで、今回はメモ的な扱いに留めておきます(図8)。かっこいいテーブル名があればお教えください。
リレーションシップを設定する
ここまでできたら、後はテーブル間のリレーションシップを設定するだけです。リレーションシップを設定するには主キーが必要になります。しかし主キーに意味を持たせてしまうと、その項目の意味付けを変えたくなったときにリレーションシップの意味まで変わってしまい困ることになります。そこで無機質な識別子を導入して、それをあたかもポインタのごとく使って参照関係を設定していきます。
まずはそれぞれのテーブルに識別子(Identifier)を導入します。単純にテーブル名+IDという項目名にしてしまいましょう(図9)。IDを設定したら、テーブル間の関係を指定していきます(図10)。
これでこの予約用紙のデータベース設計は完了です。後はそれぞれの項目のデータ型を指定していけばOKです。今回は、数値項目はINTEGER
に、文字列は全てVARCHAR(100)
にしてありますが、必要に応じて色々と試してみてください。
SQLはこうなる
でき上がったデータベース設計は、そのままほったらかしていては意味がありません。そこで実際にデータベース上にテーブルを作成してみましょう。SQLは次のようになります。
CREATE TABLE 出版社 ( 出版社ID INTEGER NOT NULL, 出版社 VARCHAR(100), PRIMARY KEY (出版社ID) ); CREATE TABLE 書籍 ( 書籍ID INTEGER NOT NULL, 書名 VARCHAR(100), 著者 VARCHAR(100), 価格 INTEGER, 出版社ID INTEGER NOT NULL, 出版年 INTEGER, PRIMARY KEY (書籍ID), FOREIGN KEY (出版社ID) REFERENCES 出版社 (出版社ID) ON UPDATE RESTRICT ); CREATE TABLE 予約者 ( 予約者ID INTEGER NOT NULL, 名前 VARCHAR(100), ふりがな VARCHAR(100), 貸出券番号 INTEGER, PRIMARY KEY (予約者ID) ); CREATE TABLE 知った ( 知ったID INTEGER NOT NULL, 新着図書案内号番号 INTEGER, 新聞名 INTEGER, 新聞掲載日付 INTEGER, 予約者ID INTEGER NOT NULL, 書籍ID INTEGER NOT NULL, その他 INTEGER, PRIMARY KEY (知ったID), FOREIGN KEY (書籍ID) REFERENCES 書籍 (書籍ID) ON UPDATE RESTRICT, FOREIGN KEY (予約者ID) REFERENCES 予約者 (予約者ID) ON UPDATE RESTRICT ); CREATE TABLE 連絡方法 ( 連絡方法ID INTEGER NOT NULL, 連絡方法名 INTEGER, PRIMARY KEY (連絡方法ID) ); CREATE TABLE 連絡先 ( 連絡先ID INTEGER NOT NULL, 予約者ID INTEGER NOT NULL, 連絡方法ID INTEGER NOT NULL, 番号 INTEGER, PRIMARY KEY (連絡先ID), FOREIGN KEY (連絡方法ID) REFERENCES 連絡方法 (連絡方法ID) ON UPDATE RESTRICT, FOREIGN KEY (予約者ID) REFERENCES 予約者 (予約者ID) ON UPDATE RESTRICT ); CREATE TABLE 図書館 ( 図書館ID INTEGER NOT NULL, 図書館名 INTEGER, PRIMARY KEY (図書館ID) ); CREATE TABLE 予約 ( 予約ID INTEGER NOT NULL, 申込日 DATE, 書名伝達可否 INTEGER, 連絡先ID INTEGER NOT NULL, 図書館ID INTEGER NOT NULL, 書籍ID INTEGER NOT NULL, 連絡不要区分 INTEGER, PRIMARY KEY (予約ID), FOREIGN KEY (書籍ID) REFERENCES 書籍 (書籍ID) ON UPDATE RESTRICT, FOREIGN KEY (図書館ID) REFERENCES 図書館 (図書館ID) ON UPDATE RESTRICT, FOREIGN KEY (連絡先ID) REFERENCES 連絡先 (連絡先ID) ON UPDATE RESTRICT );
自由にレコードを追加して、色々なSELECT
文を試してみてください。
考えすぎないようにしてみる
さて、今回は結構踏み込んだところまで考えてありますが、少し軽めにすることも見ておきましょう。例えば連絡先です。今回は予約者に附帯する形になっていますが、毎回入力してもらうという形で良いとするならば「予約」テーブルに番号を持たせてしまっても構いません。また誰が知ったのかを記録する必要がないと判断すれば、知ったという情報は書籍に附帯しますからリレーションシップを排除できます。出版社もメモ程度の扱いでいいのであれば、テーブルに持たせる必要もないでしょう(図11)。
ともすればデータベース設計を学んでいくと、ついついヘビーウェイトな設計にしてしまいがちですが、データモデルが複雑になるということはそれを扱う業務も複雑だということです。逆に言えば、元々の業務が複雑であればいざ知らず、さほどでもないものをデータモデルの理想だけで複雑化するようなことがあっては本末転倒です。データベース設計はシステム全体の下支えとなる重要なポイントですが、データベース設計だけでシステムが構築できるわけでもありません。ほどほど感を忘れないように気をつけましょう。
おわりに
いかがだったでしょうか。今回の題材はぱっと見るとシンプルに見えますが、意外と考えるポイントが多い事例です。考えるポイントが多い理由は、この題材がふたつの業務(予約とリクエスト)を兼用しているためです。もっと言うと、メモ書きとはいえ何で知ったかというものも含めると3つのイベントが混在しています。この混ざり具合に敏感になるとそれぞれを切り分けて考えることができるようになるので、データモデルを取り扱うそもそもの業務の流れに気を配る余裕ができてきます。イベントは何か? ということに目を向けるきっかけになれば幸いです。ではまた。
前回記事の補記
第1回の記事について、商品の値段が変わった場合に対する対処が抜けているのではないか、というご指摘を数人の方から頂戴しました。ご指摘のとおりです。値段が変わるのか変わらないのか、というのは題材の用紙からは読み取れません。ですので見たままをデータモデルにするということで、前回の記事はあえて省略しています。この「ビジネス上の正規化」というテーマについては、WEB+DB PRESS(技術評論社刊)のVol.11、Vol.21、Vol.28をぜひご覧いただければ幸いです。