1
ぼくはSinkCapitalギルドメンバーのTK。ダンスとSQLをこよなく愛するいたって普通の29歳だ。突然だが、今日はぼくにとって、そして世界にとって特別な日なのだ。
ちょうど40年前のこの日、歴史上のベストセラーアルバムであるスリラーが発売。マイケル・ジャクソンのキング・オブ・ポップたる地位を不動のものたらしめる一つの伝説が誕生した日。
世界中のファンとともにこの記念日を愉しもうと考え、5年前に買った少しくたびれたジャケットに身を包みながら、品川にある3つ星フレンチのカンテサンスに向かったわけだ。
これ以上ない日時と、不足のないロケーションといえる。おまけに目の前には、マイケルや互いに対して同じ想いを共有する容姿端麗な婚約者。だがそこに、乾杯のシャンパンに水を刺すモヤモヤした感情が首をもたげた。出どころのわからないその感覚に戸惑い、何度も行き止まりにぶつかりながらやっと辿り着いたその言葉。
それは言うなれば、"落ち着かない"であった。
目の前のソルベで口の中をリセットさせることはできても、あまりにも洗練された空間に圧倒されてしまって生じた内面のしこりまでは払拭することができないと今まさに身をもって知ったのである。
2
ところで、"落ち着かない"という経験は誰にでもあるはずである。自分の身が置かれている状況に、名状しがたい違和感を覚えること。そしてそれは、人の心が通っているとはにわかに信じがたいデータのプロフェッショナルたちにも共通する感情のようだ。たとえば、データベースの主キーを見るととにかく "id" というカラム名にしたくなる(=しないと落ち着かない)ことはその典型である。
主キーは特定のレコードを同定(identify)することを通じて、行の重複を避けながら正確に作業することを可能にしてくれる。データを扱う際には一番重要と言えよう。だから、"id" という言葉に特権的な地位を与えて、主キーと "id" を等号で結んで運用してしまうらしい。
それなりに根強く存在する風潮ではあるが、このような"盲目的なID信仰"の実態といくつかのデメリットについて整理したので、箸休めがてら(正確には箸ではなくナイフとフォークだが)徒然なるままに披露するものである。
3
このようなID信者はいくつかのグループに分けられるが、まずいるタイプが、必要もないのにわざわざid列を設けてそれを主キーにしたくなってしまう人だ。ちなみに、このようなid列はサロゲートキー(代替キー)と呼ばれる。それに対して、これがなくとももともと存在する別の列が主キーとして十分であれば、それは自然キーと呼ばれる。
ある組織における部内のグループ情報を格納しているテーブルを以下に示す。各グループがどの部に所属しているか、グループ責任者が誰かがわかるものであり、一番左のid列が自動採番のサロゲートキーである。
id | department_id | department_name | group_id | group_name | manager_id |
---|---|---|---|---|---|
1 | 101 | 人事部 | 1 | 企画G | g20425 |
2 | 101 | 人事部 | 2 | 厚生G | g20144 |
3 | 101 | 人事部 | 3 | 給与G | g20313 |
4 | 102 | 経営企画部 | 1 | 企画G | g21994 |
5 | 102 | 経営企画部 | 2 | 決算G | g20277 |
6 | 102 | 経営企画部 | 3 | 分析G | g20607 |
7 | 103 | 営業部 | 1 | 法人G | g19948 |
端的に言って、サロゲートキーのid列は不要で、本来であれば自然キーである {department_id, group_id } で主キーとしては十分である。特にこのように"複合主キー"となると、わざわざ新しくid列をつくりそれを主キーにしたくなってしまうのである(このように自然キーが全然複雑でない場合でもである)。
そしてまた別のタイプだが、わざわざ余分なサロゲートキーとしてid列を付与することはしなくとも、主キーの名前自体をidにしてしまう人もいる。次の3つのテーブルのイメージである。
--purchasesテーブル--
<各購入についてユーザと商品、購入数量、購入日の情報>
id | user_id | item_id | qty | date |
---|---|---|---|---|
1 | 93 | 34 | 4 | 2022-12-01 |
2 | 28 | 49 | 3 | 2022-03-29 |
--usersテーブル--
<各ユーザについて名前と性別の情報>
id | user_name | gender |
---|---|---|
28 | 浦飯幽助 | M |
29 | 雪村螢子 | F |
--itemsテーブル--
<各商品について商品名と単価の情報>
id | name | price |
---|---|---|
49 | たばこ | 600 |
50 | ガム | 100 |
4
さて、2種類の具体的な様態を紹介したが、これらの問題点を考えてみよう。言うなれば、この盲目的なidに対する信仰が、どんな悲劇的結末を生み出しかねないかを検討するのである。
一つ目は、idが一意になる代わりに、別の本質的なキーの一意性保証が骨抜きにされるということだ。さっきの組織データをもう一度見返そう。id = 8に注目してもらいたい。
id | department_id | department_name | group_id | group_name | manager_id |
---|---|---|---|---|---|
5 | 102 | 経営企画部 | 2 | 決算G | g20277 |
6 | 102 | 経営企画部 | 3 | 分析G | g20607 |
7 | 103 | 営業部 | 1 | 法人G | g19948 |
8 | 103 | 営業部 | 1 | 法人G | g21889 |
営業部の法人Gが重複してしまっていることがわかるだろう(グループ責任者に異動があった際にUPDATEではなくINSERTしてしまったようだ)。主キーのidに問題はないためこれではエラーにならずこの問題は見逃されてしまう。主キーは役割を形式的には果たしているが、実質的には果たしていないということになる。
一応の解決策として、自然キー {department_id, group_id } にUNIQUE制約をつけることは可能である。だが、それならサロゲートキーであるid列はなんのために存在するのか、いよいよわからなくなってくる。
そしてもう一つ。idという列名は抽象的な言葉のため明確な意味を持たない。一般的にSQLはわかりやすい方が優れており、したがって列名もわかりやすく明快であるべきである。個人的に一番大きな形で現れると思っているのが、結合する際のUSING構文である。
先ほどの購入明細を含む3つのテーブルを結合してみよう。
SELECT
p.date -- 購入日
, u.user_name -- 購入者名
, i.item_name -- 商品名
, p.qty * i.price as amount -- 利用金額
FROM purchases as p
LEFT OUTER JOIN items as i ON p.item_id = i.id
LEFT OUTER JOIN users as u ON p.user_id = u.id
結合条件の p.item_id = i.id
と p.user_id = u.id
にやりづらさを感じないだろうか。本来なら、
LEFT OUTER JOIN items as i USING(item_id)
LEFT OUTER JOIN users as u USING(user_id)
というようにUSING構文で済ませられた方がはるかにスッキリする。だが、これを実現するには結合キーとなるカラム名がテーブル同士で同じでなくてはならない。だから意味合いが全く異なるカラム(usersテーブルの主キーとitemsテーブルの主キーというような具合)であるにもかかわらず "id" という名称をテーブル間で共有してしまうことは避けたいのである。
5
ちなみに "id" という列名がよく使われるのには、開発サイドの工数を削減するのに役立つ仕組み(フレームワーク)が存在するから、という事情があるようだ。だが、データを分析する立場からすればそんなことは知ったことではない。USING構文を使えず、交通量の多い車道を渡るときのように「みぎみて、ひだりみて、もういちどみぎをみて」となった挙句、"事故"に遭うのは願い下げだ。今でさえidという記号がゲシュタルト崩壊を起こして迫ってきているし、結合するテーブルの見分けもつかずに目眩がするのである。
フルコースの最後に流し込んだあのコーヒーのような苦い経験はもうたくさんだ。そう思いながらぼくは早歩きでカンテサンスを後にした。
参考: 『SQLアンチパターン』(https://www.oreilly.co.jp/books/9784873115894/)