フラグを属性にするか、リレーションシップにするか


オブジェクトモデリングで何だかよく分からないことに、あるデータ項目を属性にするか関連にするかの基準があいまい、というのがある。
怪しい世界だなあと思って見ていたが、考えてみるとデータモデリングの世界もおんなじです。
あるデータ項目を「属性にするのか、新しいエンティティを生成してそこに入れるか」が、流派によって違う。
どの流派も正規形であることは変わらないのだが...


例えばRDBでは、区分コード=いわゆるフラグを、別のテーブルとのリレーションシップで表すことができる。
で、あんまりやったことないけど、これからは区分コードを極力排除して、区分をリレーションシップで表現することにしようと思ったのです。

商品マスタの中に、現役商品と廃盤商品の両方が入っているとする。

商品マスタ = { 商品ID(PK), JANコード, 商品名, 廃盤区分(Yes/No) ... }

また、商品を集めてカタログを構成する。

現行カタログ = { カタログID(PK), カタログ名, 有効期間開始日, 有効期間終了日 ... }
現行カタログ.商品 = { カタログID(FK), 商品ID(FK) }

ここで、こんなビジネスルールがあったとしても

  1. 現行カタログには「廃盤」商品を追加できない
  2. 現行カタログに載っている商品は「廃盤」に変更できない

上記データ構造ではこのルールを保全できない*1
なので、アプリケーションがデータ更新前に忘れずにチェックしなくてはならない。


ここで廃盤区分を、属性ではなくリレーションシップで表現してみる。
「廃盤済み」フラグを廃止し、「現行商品マスタ」を作成すると、データ構造で上記2点を保障できる。

商品マスタ = { 商品ID(PK), JANコード, 商品名,  ... }
現行商品マスタ = { 現行商品ID(FK,UNIQUE) }              //商品マスタ.商品IDを外部キー参照する
現行カタログ.商品 = { カタログID(FK), 現行商品ID(FK) }  //現行商品マスタ.現行商品IDを外部キー参照する

つまり、廃盤商品を現行カタログに突っ込めないし、現行カタログに載っている商品を廃盤にする(=現行商品マスタからレコードを削除する)こともできない。

検索も速いかも

マスタテーブルには検索条件になる属性がいっぱいあるので、インデックスをたくさん付けたくなる。
でも検索対象になる全項目にインデックスを付けるのは気が引ける。
1テーブルにいくつまでインデックスつけてもいいのか書いてないかなと思って本読むと「インデックスが多すぎると更新が遅くなりすぎるので注意しましょう」みたいな超トートロジカルな説明しか書いてない*2
マスタが属性を大量に持って横長になるほど、インデックスのない検索項目が増えていく。


さて、フラグを外出しにすることで、現行商品の一覧を引くクエリは以下のように変化する。

(A)  select * from 商品マスタ where 廃盤区分=No
	    ↓
(B)  select * from 現行商品マスタ inner join 商品マスタ on 現行商品マスタ.現行商品ID=商品マスタ.商品ID

これ、どっちが速いか。
まず、(A)の廃盤区分にインデックスがあれば、Aの方が速いだろう。(B)でも実用的な速度が出ると思うけど。
が、その他にも検索条件となる属性がある中で、廃盤区分にインデックスを確保できるだろうか。
仮に廃盤区分にインデックスがなく、現行商品が廃盤商品よりもぐっと少なければ、(B)の方が速いだろう。


テーブルが増えるほどインデックスの確保はやりやすくなる。インデックスは join のオーバーヘッドを払ってでも欲しいものだ。
検索結果が小数(1万件中50件とか)なら、結合済みのデータをインデックスなしで舐めるより、joinしまくってもindex scanした方が速いはずだ。

*1:ここちょっと自信ない。検査制約(checkとかcreate assertionとか)で別テーブルのデータもチェックができるdbmsもあるのかもしれない。少なくともPostgreSQL/MySQLでは無理っぽい

*2:正美氏の昔の本には「OracleDB2は5つまで。SQL ServerPostgreSQLは3つまで」と明快に書いてあったけど、今はどうなんだ