テーブルをつなぐ
注文に顧客の名前を付ける。JOIN で行が増える・消える感覚、LEFT JOIN、中間テーブル、なぜテーブルを分けるのか。
- 注文の表には顧客名がない。どこから持ってくるか
- 2 つの表をつなぐと、行数はどうなるか
- なぜ最初から 1 つの表にしないのか
実物: 名前のない注文
customer_id に 1 とか 3 とか入っている。この数字は customers テーブルの id で、「顧客 1 の注文」という意味だ。 名前が欲しければ customers を見に行く必要がある。手でやるなら、注文 1 行ごとに customers を id で引く。それを SQL でやるのが JOIN だ。
JOIN: 対応する行をくっつける
FROM orders o JOIN customers c ON c.id = o.customer_id と読む。「orders の各行について、customer_id と id が一致する customers の行をくっつける」。o と c は表の別名で、両方に id という列があるので o.id、c.id と区別する。ON のあとが「何をもって対応とみなすか」の条件だ。
チケット前半はこれで終わり。ここから、JOIN で起きることをもう少し見る。
行が増える、行が消える
今度は逆に、customers を土台にして orders をくっつける。customers は 10 行だ。行数を予測してから実行する。
50 行のはずだ。10 行ではない。佐藤さんには注文が 12 件あるので、佐藤さんの行が 12 回出る。JOIN の結果の行数は「対応の組み合わせの数」で、 1 人に注文が多数あれば、その人の行は注文の数だけ増える。
増えるだけでなく、消えもする。中村さんと加藤さんは注文がないので、対応する orders の行がなく、結果に出てこない。 これが前章で「0 件の顧客が消えた」のと同じ現象で、ここまでの JOIN(INNER JOIN)は「対応がある行だけ」を返す。
LEFT JOIN: 左側は全部残す
LEFT JOIN は、左(FROM に書いた側)の行を必ず残す。対応する右の行がなければ、右の列は NULL で埋める。 ここで第 1 章の NULL が「値がない」ではなく「対応する行がない」という意味で出てくる。 増える(佐藤)・消える(中村)・NULL で残る、の 3 つを 1 枚で見る。
前章の COUNT(*) と COUNT(列) の違いがここで効く。中村さんの行は 1 行あるが、o.id は NULL なので COUNT(o.id) は 0 になる。COUNT(*) にすると NULL の行も 1 と数えて、注文 0 の人が 1 になってしまう。
LEFT JOIN + IS NULL は「対応がない行」を探す定番の形だ。
驚き: LEFT JOIN なのに行が減る
「顧客一覧に、キャンセルされた注文を並べる(キャンセルのない人も残す)」を書く。行数を予測してから実行する。
2 行のはずだ。10 行残るつもりで LEFT JOIN を書いたのに、キャンセルのある 2 人しか残らない。 原因は WHERE の位置で、LEFT JOIN で右側が NULL になった行は、o.status = 'cancelled' を評価すると「不明」になって落ちる(第 2 章の 3 値論理)。 右側の条件は ON に書く。
「LEFT JOIN なのに行が減った」と言われたら、WHERE に右側の列がないかを見る。ほぼそれだ。
上の 2 つのラボ(WHERE で右側を絞る = 2 行、ON に書く = 10 行)から、鈴木・田中・中村の 3 人を抜き出して、条件がどこで効いたかを並べる。
3 つ以上をつなぐ、多対多をつなぐ
注文明細(order_items)には商品の番号だけがある。注文 → 明細 → 商品と 2 回つなげば、「誰が何を買ったか」が出る。
商品とカテゴリは「1 つの商品に複数のカテゴリ、1 つのカテゴリに複数の商品」の関係で、これは orders → customers のような「多対 1」では表せない。product_categories(product_id, category_id) という、組み合わせだけを持つ表を間に置く。中間テーブルと呼ぶ。
なぜ最初から 1 つの表にしないのか
JOIN は手間だ。注文の表に顧客名とメールを最初から入れておけば、つなぐ必要がない。それをしない理由を図で見る。
注文が 12 件ある佐藤さんのメールを、注文の表に 12 回書くと、佐藤さんがメールを変えたとき 12 行を直す必要があり、1 行直し忘れると 2 つのメールが混在する。 「同じ事実を 2 か所に書かない」ように表を分けることを正規化と呼ぶ。分ける代わりに、読むときにつなぐ手間(JOIN)を払う。 第二部の 0 章で、あえてこの原則を破って列を複製する判断が出てくる。
表と表の「指す・指される」関係は、宣言できる。orders.customer_id が customers.id を指す、と宣言したものを外部キーと呼び、 存在しない顧客の注文を入れようとすると DB が拒否する。
JOIN は対応する行をくっつける。結果の行数は対応の組み合わせの数で、INNER は対応がない行を消し、LEFT は左側を残す。右側の条件は ON に書く。
確認
注文のない顧客も一覧に出すには、JOIN のどこを変えるか。
LEFT JOIN で書いたのに行が減ったとき、まず何を疑うか。
LEFT JOIN の結果で注文数を数えるとき、COUNT(*) ではなく COUNT(o.id) を使うのはなぜか。
次の章へ
ここまでは読むだけだった。次の章では注文を入れ、状態を変え、消す。そこで「WHERE を忘れた UPDATE は全行を変える」という、本番で最も怖い事故を、小さな表で先に起こしておく。