行持ち・列持ち (横持ち)

ちょっとまとめ。

行・列

行と列は漢字からイメージすると覚えやすい。

例えば、以下のテーブル

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 とは言えないし、パフォーマンス的にもあまりいいものではない。

一行のサイズが大きくなる

当然、列を増やすと言うことはその分必要なサイズが大きくなる。
RDBMS では NULL を特別扱いしており、NOT NULL でない列には NULL かどうかのフラグも持たせることになるため、さらにサイズに与えるインパクトは大きなものになる。
行のサイズが大きくなりすぎると、パフォーマンスに与える影響も無視できなくなる。


更に、分類情報が必要ないところでも、格納方法によってはパフォーマンスに対して負の影響を与えることとなる。

行持ち

では、行持ちではどうなるのかを見てみよう。
まず、商品テーブルは以下のようになる。

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ことが多いため、別物と思っておいた方がいいだろう。

*1:このテーブル自体は行持ち

*2:足りない情報を安直に列に追加していく