コンピューターサイエンス:アルゴリズム / OS / ネットワーク / DB
第1正規形
カンマ区切りにした列は、二度と検索できない
利用者の電話番号を 1 つの列に入れておいて、2 つ目が必要になったのでカンマで区切って足した。よくある成り行きです。
| 利用者 ID | 氏名 | 電話番号 |
|---|---|---|
| 1 | 佐藤 | 090-1111-1111, 080-2222-2222 |
| 2 | 鈴木 | 070-3333-3333 |
しばらくは動きます。表示するだけなら、そのまま画面に出せば済むからです。
崩れるのは、その列で何かをしたくなったときです。「080 で始まる番号を持つ人」を探すには、文字列の途中に含まれるかどうかで探すしかありません。この探し方では索引が使えず、必ず全件を読みます。「2 つ目の番号だけ直す」には、何文字目から何文字目かを数える処理が要ります。「番号を 1 本しか持たない人」を数えるには、カンマの個数を数えることになります。
そして、同じ番号が 2 回入っていても、桁数がおかしくても、DB は何も言いません。1 つの列に複数の値が入っている限り、値そのものに制約を掛けられないからです。
入力の側でも守れません。1 本ずつ入力させる画面なら形式を確かめられますが、まとめて 1 つの文字列にしてから保存すると、確かめる場所が消えます。区切り文字を全角で打った 1 件が、そのまま通ります。
別の表に出すと、検索できる形に戻る
直し方は、多値の列を別の表へ移すことです。利用者 1 人につき、番号の数だけ行を作ります。利用者 ID と番号の 2 列だけの、素っ気ない表になります。
こうすると、番号は 1 行 1 値になります。「080 で始まる」は前方一致になるので索引が効き、重複禁止の制約も掛けられ、番号を 1 本消すのは 1 行消すだけになります。
表を分けると、番号そのものにも情報を持たせられます。どれが主に使う番号かを表す列を足す、種別を分ける、登録した順を持たせる。カンマ区切りの文字列には、こうした付随情報を足す場所がありません。増やしたければ区切り文字をもう 1 種類決めるしかなく、そこから収拾がつかなくなります。
元の表に行を増やして、同じ人の氏名を 2 回書く手もありますが、これはやめておきます。今度は氏名が 2 か所に書き写され、名前を変えるときに同じ取りこぼしが起きます。1 つ直したつもりが、別の場所に同じ問題を作っただけになります。
JSON の列は違反なのか
いまの DB は JSON 型を持っています。1 つのセルに構造が入るので、形の上では同じ話に見えます。
判断は用途で分かれます。その中身で検索したり、集計したり、他の表とつないだりするなら、外に出して普通の列にします。API の応答をそのまま控えておく、設定のスナップショットを残す、といった「読み返すだけの付随情報」なら、JSON のままで構いません。関数索引などで部分的に引く手段はありますが、本気で検索するなら普通の表のほうが速く、制約も掛けられます。
配列型を持つ DB もありますが、判断の軸は変わりません。並べて表示するだけなら便利で、その中身で他の表とつなぎたくなった時点で限界が来ます。
住所を 1 本の文字列で持つのも似た話です。表示するだけなら 1 列で十分ですが、市区町村ごとに集計したくなった時点で分ける必要が出ます。粒度の決め方は「その単位で検索するか、更新するか、集計するか」であって、理論上の最小単位まで割ることではありません。
1 つのセルには 1 つの値だけ、という状態が第 1 正規形です。