行持ち・列持ち (横持ち)
ちょっとまとめ。
行・列
行と列は漢字からイメージすると覚えやすい。
例えば、以下のテーブル
col_a | col_b | col_c | col_d |
---|---|---|---|
1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 |
なら、一行目は 1, 2, 3, 4 だし、三列目は 3, 7 となる。
列持ち (横持ち)
では、列持ちとはどういうテーブルのことを言うのかというと、情報を「列で」持っているテーブルのことを言う。
なぜ「横」持ちかというと、横に何列もあるからであって、列自体の方向とは関係はない。
例えば、商品の分類を「横持ち」しているテーブルは以下のようになる。
id | name | group_1 | group_2 | group_3 | group_4 | group_5 | group_6 | group_7 | group_8 | group_9 | group_10 |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | aaa | Y | N | N | N | N | N | N | N | N | N |
2 | bbb | N | N | N | Y | N | N | N | N | N | N |
3 | ccc | N | N | N | N | N | N | N | N | Y | N |
4 | ddd | Y | N | N | N | N | N | N | N | N | N |
この設計方法の欠点は、変更に弱い点と、SQL が複雑になる点、一行のサイズが大きくなる点などが挙げられる。
変更に弱い
例えば、分類が 100 個に増えた場合、テーブル構成を変える必要がでてきてしまう。
(まずないが) 逆に、分類が減った場合にもテーブル構成を変える必要がある。
SQL が複雑になる
商品テーブルの他に、商品分類テーブルがあったとする*1。
id | name |
---|---|
1 | group 1 |
2 | group 2 |
3 | group 3 |
4 | group 4 |
5 | group 5 |
6 | group 6 |
7 | group 7 |
8 | group 8 |
9 | group 9 |
10 | group 10 |
商品名と分類名の一覧を取得したい場合、以下のような SQL が必要となる。
SELECT Item.name , ItemGroup.name FROM Items Item INNER JOIN ItemGroups ItemGroup ON ItemGroup.id = CASE WHEN Item.group_1 = 'Y' THEN 1 WHEN Item.group_2 = 'Y' THEN 2 WHEN Item.group_3 = 'Y' THEN 3 WHEN Item.group_4 = 'Y' THEN 4 WHEN Item.group_5 = 'Y' THEN 5 WHEN Item.group_6 = 'Y' THEN 6 WHEN Item.group_7 = 'Y' THEN 7 WHEN Item.group_8 = 'Y' THEN 8 WHEN Item.group_9 = 'Y' THEN 9 WHEN Item.group_10 = 'Y' THEN 10 END
これはお世辞にも読みやすい SQL とは言えないし、パフォーマンス的にもあまりいいものではない。
行持ち
では、行持ちではどうなるのかを見てみよう。
まず、商品テーブルは以下のようになる。
id | name | g_id |
---|---|---|
1 | aaa | 1 |
2 | bbb | 4 |
3 | ccc | 9 |
4 | ddd | 1 |
そして、商品分類テーブルは変わらない。
id | name |
---|---|
1 | group 1 |
2 | group 2 |
3 | group 3 |
4 | group 4 |
5 | group 5 |
6 | group 6 |
7 | group 7 |
8 | group 8 |
9 | group 9 |
10 | group 10 |
これだけでも、サイズに与えるインパクトが分かってもらえると思う。
更に、商品名と分類名の一覧を取得したい場合の SQL は、
SELECT Item.name , ItemGroup.name FROM Items Item INNER JOIN ItemGroups ItemGroup ON Item.g_id = ItemGroup.id
でいい。
商品分類を増やすのは、商品分類テーブルに追加するだけだし、減らす場合は商品分類テーブルから削除するだけだ。
更に、Items.g_id と ItemGroups.id にリレーションシップを設定しておけば、商品テーブルで使っている分類を削除できないようにすることも容易に出来る。
これは少し特殊な「列持ち」の例なのだが、それでも列持ちからくる不便さはよく分かってもらえると思う。
非正規化と列持ちを同じように扱う場合があるが、非正規化とは「正規化した上でパフォーマンスを考慮して正規化をわざとくずす」ことであり、列持ちははじめから正規化なんて考えていない*2ことが多いため、別物と思っておいた方がいいだろう。