"1 Fact in 2 Place"はダメ、"2 Fact in 1 Place"もダメ

会社の研究会の課題図書である、Web+DB Press vol.28の羽生章洋氏の記事「EJB3時代のデータベース設計」を読む。
正規化の方針でよく言われる"One Fact in One Place" は
「1つのデータを2箇所にしまうな」
という意味だと思っていたが、この記事を読んで逆方向の意味
「2つのデータを1箇所にしまうな」
という戒めでもあることに気づかされた。

さらに以下の記述から、"2 Fact in 1 Place"の間違いが個々のデータ項目だけのものではないことにも気づく。

正規化というと、1つの事実を2つ以上の場所に置くのを排除するということに目が向きすぎるあまり、分けて保持すべきものまで一緒にしてしまい窮屈なデータ構造にしてしまうケースによく出会います。
そうなってしまうと、結局はプログラミング言語でどうにか見分けることになり、見分けるためにxx区分のようなものが導入され、それを判定するIF文が増えていきどんどんロジックの複雑さを促進してしまうのです。

これはサブセット化によるIFネストの排除の話だろう。
とすれば、2 Fact in 1 Placeなアンチパターンには以下の2つのレベルがある*1

  • 列のレベル
    • 受注明細の「単価」を品目マスタの「単価」に統合してしまったために、注文単位に値引きされた単価を記録できなくなってしまった
  • テーブルのレベル
    • 「顧客マスタ」と「顧客マスタ変更履歴」を分けなかったので、現在の顧客情報を取得するためにいちいち集計関数を使わなくてはならなくなった
    • 「現役社員/退職社員」、「正社員/パート・アルバイト」、「役員/非役員」の区分にかかわらず、全社員の情報を「社員マスタ」に投入したために、給与計算アプリケーションがIF分岐のかたまりになってしまった
結論

「重複の排除」を安易に行ってはならない。
更新時異状の回避のために行う重複の排除は必要だが、

  • 重複しているとディスクスペースがムダだから
  • 同じ情報を複数箇所に書くと、書き込み時のパフォーマンスが下がるから

みたいな理由でデータを統合しようと思った時は、「ほんとに二重に取っとかなくていいのか?」を自問するようにしよう。
例えば「顧客名」が表示されている受注伝票をシステム化する場合、反射的に

  • 受注ヘッダに「顧客ID」があって、顧客マスタを外部キー参照している
  • 顧客名は顧客マスタ上のみに存在する

という形を連想するが、それでよいとは限らない。
必ずお客さんに「顧客の会社名が変わった時、3年前の受注伝票上の顧客名まで、新しい会社名に変わってしまっていいですかね?」*2と確認し、「そりゃダメだよ」と言われたら、受注ヘッダに「顧客名」を重複して持たせるとかしなくてはならない。

*1:もしかしたら、第3のレベル「データベースのレベル」もあるのかもしれない。

*2:いいわけがないと思うのだが、こういう問題のあるシステムを作ったこと何度かある。