[DBデザイン#17] 実例から考える: 変更ポイントを考える

一連のデータベースデザインのお話は、仕様変更のことに入っていますが、当然ながら、設計の初期段階で「今まで聞いてなかった仕組みを実装しないといけない」というような場合でも通じる話です。その場合、まだ、実装をしていないという場合には、かなり自由に、そして理想的な状態での機能追加ができるのですが、前回説明したように実装をしてしまうと品質低下と戦うことになります。

さて、納品書に出荷日が必要であり、出荷が複数になる場合もあることがわかりました。ここで、「出荷」というエンティティは明らかに存在するとして、納品書、販売明細と出荷がどんな関係になるのかということを決めなければなりません。だいたいでいいということはなく、決めないといけません。

ここでまず、表としてデータを展開してみます。その後に「出荷」の概念の分離をしたいと思います。最初の案は、出荷日にカンマ区切りで記載されていたので、そのような出荷日フィールドを用意することです。1フィールドの追加なので、さほど大きな変更ではなさそうです。もちろん、第一正規形を満たさないので分割するという王道な意味での設計原則はすぐに思いつくことです。具体的には、特定の出荷日の納品書を集めるというのが、複雑な検索条件になることや、出荷日がDATE型ではなく文字列になるため、日付として正しい形式でデータを追加したり分離したりといった処理が増えるといったデメリットがあります。しかしながら、見えづらいデメリットですが、販売明細の1行1行について、いつ出荷したのかはわかりません。それを知らなくてもいいのかどうかもわかりません。いずれにしても、これは「見かけを整える」だけであって、その後の処理にはあまり良い方法ではなさそうです。

もう少し、出荷日について、DATE型のフィールドを用意した状態で進めるとしたら、フィールドを複数用意することになるでしょう。FileMakerの繰り返しフィールドも本質的にはこの方法になります。まず、これで見てわかることは、3回以上の出荷があったら記録できないなど、一見して問題点がわかります。もちろん、前者の問題点は同様にあります。

明細に対して、いつ出荷するかを記録してみる方法はどうでしょう?次のように「出荷日」を販売明細側に持たせます。このデータでは顕在化していませんが、同一日に別々に出荷があっても、1回でまとめて出荷した場合と区別がつきません。それでいいのかもしれませんが、ダメかもしれません。また、納品書側に表示する方法も若干SQLが複雑になります。ですが、納品書の表に出荷日を持たせるよりも、柔軟に対応できるのかもしれません。

さて、ここで実は大きな問題が見えて来ます。そもそも、出荷はどうやっているのか? 出荷はおそらくいろんな商品をまとめて送るでしょう。納品書通りの場合もあるでしょうけど、在庫がない場合にはあるものだけを送ったりするのが一般的かと思います。その時、販売明細の1行に忠実に出庫するかどうかは、その会社のワークフローそのものに関わることです。仮にその仮定が正しいとすると、販売明細は、出荷の明細としても利用できて、システム的にはシンプルになります。しかしながら、人間がそういった処理をするときには、販売明細とはあまり関係なく、ある顧客に対するある商品の未発送数を把握して、その未発送数ないしはそれより少ない数を1つの出荷明細として管理するようになるのではないでしょうか? 紙の台帳のような仕組みでそういうことを倉庫部門で管理している可能性もあるのです。

そこまで考えるとさらにややこしくなって来ました。このような複雑な問題は、やはり分割して考えます。まず、「納品書と出荷の明細は対応している」という状態を考えた上で、その後に「対応してない」という状況を考えることにしましょう。実際に仕様を考えるとき、疑問が発生したら即座にそれに答えてくれると言ったような状況にはならず、お客さんに問い合わせて、場合によっては検討の時間が必要になるかもしれません。では待つのかというと、そうではありません。設計するときには、結果的にあらゆる可能性を考えることになります。問題点や、利点、その後の開発作業等を、いろんな可能性に対して思い浮かべ、記述します。なので、確認作業をしながらも、可能性の追求をし続けます。おそらく、そうした想定のどれかに落ち着くものです。

「納品書と出荷の明細は対応している」前提での新たな表の導入をしてみます。おそらくは、2つのアイデアのどちらかになります。1つは以下のように、既存の納品書と販売明細の関係を崩さないように、納品書と出荷予定が1対多の関係になるようにすることです。新たに「出荷予定」という表を用意します。若干、具体的に「予定」という言葉を補うことにします。納品書、販売明細に変更はありません。おそらく、改変のための作業は少ないと見込めるでしょう。出荷予定の表は、1回の出荷が1レコードとなります。出荷IDはここでは便宜上のもので、納品書IDが記録されているところがポイントです。これにより、ある1つの出荷は特定の納品書に対するものとなります。もちろん、納品書と出荷が対応していないとなると、話は崩れるので、まずは、シンプルな状況を考えることにします。なお、この設計だと、販売明細の1行1行に対して、それがどの出荷予定なのかはわかりません。単に出荷の日付を記録するというコンセプトのものであって、納品書に追加した出荷日フィールドを第一正規形に従うように表の分離をしたものとも言えます。

ここでの出荷予定の表にある実質的なフィールドは出荷日だけです。現実の出荷にはもっとフィールドが必要かもしれませんが、出荷日に対して1対1になる存在が「出荷予定」とも言えるでしょう。この仕様変更の1つの目的には「複数ある出荷日を記録する」ということであり、それだけでいいのなら、このスキーマで十分です。ポイントは、出荷日が納品書ではなく、別の表にあるということです。何度も説明していますが、これがリレーショナルデータベースの設計の1つの重要な考え方になり、その導入には1対多の関係を抽出するということなのです。

一方、別の考え方として、納品書はいくつかの出荷に分離され、その出荷に対してどの商品をいくつ送るのかという明細情報があるという考え方です。ここで、販売明細からは納品書IDはなくてもいいので、一旦消してあります。その代わり、どの出荷に対応するものなのかを示す出荷IDのフィールドがあります。出荷が1回でも、あえて出荷の表に行を追加しなければなりません。大まかに言えば、すでに開発が始まっているとしたら、変更箇所は多そうです。要するに「出戻りが〜」と言われるような改変です。

ここで、設計段階で考えることに、開発可能で理解可能なユーザインタフェースが作れるのかということがあります。ER図やクラス図ではその考え方は一般には明示的にはなっていないので、設計者が気づかないといけません。これらのダイアグラムは一般には静的、つまりシステムの処理が一段落しているときに、データが正しく存在しているかを示すのであって、そこに至るまでの問題点は記述されていないのが一般的です。

そのことをどう考えるのか? それは、表に対するCRUD、つまりCREATE(新規作成)、READ(読み出し)、UPDATE(更新)、DELETE(削除)がそれぞれスムーズにできるためにはどんな前提があって、どんなユーザインタフェースが適しているかということを考えます。もちろん、検討のポイントはREADを除く3つの更新系処理です。当初の設計は、納品書と販売明細がありましたが、この2つだけです。2つだけだと、結構話は早く、納品書の行が先に作られて、そこに販売明細が順次いくつか作られるというCREATEの段取りはすぐにわかります。DELETEは各行で考えればよく、カスケード削除にするかどうか、論理削除(ソフトデリート:実際にデータは削除しないでフラグ等で削除したことにする手法)にするかくらいの検討になります。ちなみに、1対多の場合、1が先に作られるという前提があると、そんなに複雑にはなりません。

しかしながら、最後の図のように、1対多の連鎖があると、実はかなり難しくなります。最初に納品書の行を作るとき、自動的に1つ目の出荷予定を作るという工夫はまずは思いつきます。そして、その後、販売明細が追加されていくのですが、そのとき、出荷予定と紐づく形で販売明細が作られます。これはボタンを押して画面遷移をさせれば作れるのですが、おそらくかなり面倒なUIになりそうです。画面遷移をあまりせず、それでいて出荷予定とうまく紐づくという意味で、販売明細のCREATEやUPDATEを実現するUIはどうすればいいでしょうか? 適当に作ると、きっとダメ出しされてしまいます。プロトタイプを作って検討するなど、出戻りが少ない作業の段取りを考えるべきです。ここで、出荷予定が変わった、出荷が後から無くなった場合はどうしましょう?このように3つのエンティティが関わると、その途中がなくなるということも発生します。削除する出荷予定に紐づく販売明細は、そこで繋がりが切れるので、別の出荷予定に付け替えないといけません。もちろん、明細を持つ出荷は削除できないという仕組みは必要でしょうけど、いずれにしても、明細を作って削除するだけでなく、どの明細に紐づくのかという結合先のUPDATE機能、つまりちょっと特殊なUIが必要になります。

結果的に、全ての表に対してのC_UD(Rを除く。ですが、現実にはUPDATEとREADは密接に関連します)の必要性を考え、それがスムーズにできるのかどうかを考えた上で、最終的なスキーマに落ち着くでしょう。スキーマだけの問題ではないのです。ロジックやUIを含めて考えるのが設計になります。

仕様変更があるとこのようにして、UIは込み入り、お客さんが「こうして欲しい」というのが実現できないので代替案を考えて交渉するなど、一気に複雑さが増します。データベースのスキーマ変更は、システム全体に波及することがあるので、なるべくそうならないようにしたいとも考えるところですが、やはり要求実現という考え方からするとどうしてもやらないといけなくなるかもしれません。ちなみに、出荷予定を導入した2つのスキーマのうち、後者のような運用をしているところはあまりないと思われますが、UIが複雑化する絶好の例だったので紹介してみました。単に出荷日を記録するだけで良いという要求であれば、前者のスキーマにするか、販売明細側にフィールドを作るかというところで、なるべく改変箇所が少なくなるように考えるのが一般的であると思われます。販売明細側にフィールドを作る方法も、「出荷日を変更する」というUIが込み入りそうですので、具体的に考えてみるのもトレーニングになりそうです。

次回は、「納品書と出荷の明細は対応していない」前提でスキーマ変更を考えてみましょう。