概要(JOIN は「バラバラの表をつないで、1 枚の結果にする」)
JOIN は、SQL の中でも「一段レベルが上がった感じ」がするところですが、
本質はとてもシンプルで、
「関連する 2 つ以上のテーブルを“つないで”、1 つの結果として見る」
ための仕組みです。
ユーザーは users テーブル
そのユーザーの注文は orders テーブル
のように、現実的なデータベースは必ず「テーブルが分かれて」います。
JOIN を理解すると、
「ユーザーとその注文を一緒に見たい」
「本とその著者名を一緒に出したい」
といった“当たり前にやりたいこと”が、ちゃんとできるようになります。
ここでは、まず「JOIN のイメージ」を固めてから、
INNER JOIN、LEFT JOIN を中心に、例題付きでかみ砕いていきます。
JOIN のイメージを先に固める
2 つの表を「共通のカギ」でくっつける
まずは、次の 2 つのテーブルを考えます。
users テーブル
id | name
---+--------
1 | Taro
2 | Hanako
3 | Ken
orders テーブル(注文)
id | user_id | item | price
---+---------+-----------+------
1 | 1 | Book | 1000
2 | 1 | Pen | 200
3 | 2 | Notebook | 500
users は「ユーザーの一覧」、orders は「注文の一覧」です。
orders.user_id は、users.id を指している「外部キー」のイメージです。
ここで、「誰が何をいくらで買ったか」を一覧で見たいとします。
つまり、こういう結果が欲しいわけです。
name | item | price
-------+----------+------
Taro | Book | 1000
Taro | Pen | 200
Hanako | Notebook | 500
この「users と orders をくっつけて 1 枚の表として見る」ために使うのが JOIN です。
INNER JOIN(最も基本の JOIN)
INNER JOIN の基本構文
一番よく使う JOIN が INNER JOIN です。
基本形はこうです。
SELECT 列...
FROM 左側のテーブル
INNER JOIN 右側のテーブル
ON 左側.カギ = 右側.カギ;
SQL先ほどの例で、「ユーザー名と注文内容を一緒に取りたい」なら、こう書きます。
SELECT users.name, orders.item, orders.price
FROM users
INNER JOIN orders
ON users.id = orders.user_id;
SQLここでやっていることは、
users の 1 行と
orders の 1 行を
「users.id = orders.user_id」という条件でペアにする
ということです。
結果はこうなります。
name | item | price
-------+----------+------
Taro | Book | 1000
Taro | Pen | 200
Hanako | Notebook | 500
Ken は注文していないので、結果には出てきません。
INNER JOIN は、「両方のテーブルに“対応する行”があるものだけ」を残す JOIN です。
ON 句が「どうつなぐか」を決める心臓部
INNER JOIN で一番大事なのは ON 句です。
ON users.id = orders.user_id
SQLここを間違えると、全く意図しない結果になります。
JOIN は「テーブル同士をどう結びつけるか」を自分で指定する必要がある、という点が重要です。
Django でいうと、User と Order が ForeignKey(User, ...) でつながっていて、Order.objects.select_related("user") したときに裏で発行される SQL が、
まさにこの JOIN です。
LEFT JOIN(片側は全部残し、もう片側はあればくっつける)
「ユーザーは全員出したい。注文がなくても出したい」ケース
INNER JOIN は「両方にデータがあるものだけ」でした。
でも、こういう要件もよくあります。
「ユーザーは全員一覧に出したい。
注文がある人は注文も一緒に出したい。
注文がない人も“注文なし”として出したい。」
さっきの users / orders の例で言うと、
Ken も「注文なし」として出したいわけです。
このときに使うのが LEFT JOIN です。
SELECT users.name, orders.item, orders.price
FROM users
LEFT JOIN orders
ON users.id = orders.user_id;
SQL結果はこうなります。
name | item | price
-------+----------+------
Taro | Book | 1000
Taro | Pen | 200
Hanako | Notebook | 500
Ken | NULL | NULL
LEFT JOIN は、
左側のテーブル(FROM の直後に書いた方)は「全員残す」
右側のテーブル(JOIN された方)は「対応する行があればくっつける。なければ NULL」
という動きをします。
LEFT JOIN の「左側が主役」という感覚
LEFT JOIN を読むときは、
「左側のテーブルが主役」と考えると分かりやすいです。
今回の例では、
主役は users(全員出したい)
orders は「もしあれば」くっつける
という設計です。
もし逆に、
「注文は全部出したい。ユーザー情報は、あればくっつける」
という要件なら、FROM を orders にして LEFT JOIN users します。
SELECT users.name, orders.item, orders.price
FROM orders
LEFT JOIN users
ON users.id = orders.user_id;
SQLこのように、「どっちを主役にしたいか」で、
FROM に書くテーブルと LEFT JOIN に書くテーブルを決めます。
JOIN と WHERE の組み合わせで「欲しい行だけ」に絞る
JOIN したあとに WHERE で条件をかける
JOIN は「テーブルをくっつける」だけで、
「どの行を残すか」は WHERE で決めます。
例えば、「Taro の注文だけを見たい」なら、こうです。
SELECT users.name, orders.item, orders.price
FROM users
INNER JOIN orders
ON users.id = orders.user_id
WHERE users.name = 'Taro';
SQL処理のイメージは、
まず JOIN で users と orders をくっつける
その結果から、name = ‘Taro’ の行だけを WHERE で残す
という流れです。
LEFT JOIN と WHERE の組み合わせでハマりやすいポイント
LEFT JOIN のときに、WHERE の書き方で意味が変わるパターンがあります。
例えば、「ユーザーは全員出したいが、注文がある人だけに絞りたい」場合を考えます。
次の 2 つを比べてみてください。
パターン A
SELECT users.name, orders.item, orders.price
FROM users
LEFT JOIN orders
ON users.id = orders.user_id
WHERE orders.id IS NOT NULL;
SQLパターン B
SELECT users.name, orders.item, orders.price
FROM users
LEFT JOIN orders
ON users.id = orders.user_id;
-- WHERE なし
SQLパターン A は、「LEFT JOIN したあとで、orders.id が NULL ではない行だけを残す」ので、
結果的に「注文があるユーザーだけ」になります。
つまり、INNER JOIN と同じような結果になります。
パターン B は、「LEFT JOIN した結果をそのまま全部出す」ので、
注文がないユーザーも NULL 付きで出てきます。
LEFT JOIN を使うときは、
「本当に NULL 行も残したいのか」
「WHERE で NULL を除外してしまっていないか」
を意識して読むのが大事です。
JOIN を 2 つ以上つなげる(3 テーブル以上の JOIN)
ユーザー、注文、商品テーブルをつなぐ例
現実のアプリでは、2 テーブルだけで完結することは少なく、
3 テーブル以上を JOIN することもよくあります。
例えば、次の 3 つのテーブルを考えます。
users テーブル
id | name
---+--------
1 | Taro
2 | Hanako
orders テーブル
id | user_id | product_id
---+---------+-----------
1 | 1 | 10
2 | 1 | 11
3 | 2 | 10
products テーブル
id | name | price
---+-----------+------
10 | Book | 1000
11 | Notebook | 500
「誰がどの商品をいくらで買ったか」を一覧で見たいとします。
このときは、users と orders、orders と products を順番に JOIN します。
SELECT users.name AS user_name,
products.name AS product_name,
products.price
FROM users
INNER JOIN orders
ON users.id = orders.user_id
INNER JOIN products
ON orders.product_id = products.id;
SQLJOIN は「何回でも書ける」ので、
必要なだけテーブルをつなげていくイメージです。
ここでも大事なのは、
それぞれの ON 句で「どうつなぐか」を正しく書くことです。
JOIN を理解するための“頭の中のシミュレーション”
手で「ペアを作る」イメージを持つ
JOIN が難しく感じるときは、
頭の中で「手作業でペアを作る」イメージを持つと楽になります。
INNER JOIN users × orders(ON users.id = orders.user_id)なら、
users の 1 行を取り出す
orders の全行を見て、「user_id が同じ行」を探す
見つかったら、その組み合わせを 1 行として結果に追加する
users の次の行に進んで、同じことを繰り返す
という作業を、データベースが高速にやってくれているだけです。
LEFT JOIN の場合は、
users の 1 行を取り出す
orders の中から対応する行を探す
見つかればペアにして結果へ
見つからなければ、orders 側を NULL にして結果へ
という違いがあります。
この「手作業シミュレーション」ができるようになると、
JOIN の結果が直感的に予想できるようになります。
まとめ(JOIN は「分割された世界を、必要なときだけ一時的にくっつける」)
JOIN を初心者目線で整理すると、こうなります。
テーブルは「正規化」のために分割されているので、現実的なアプリでは「ユーザー」「注文」「商品」などが別テーブルになっている。
JOIN は、それらを「共通のカギ(外部キーなど)」で一時的にくっつけて、「誰が・何を・いくらで」のような情報を 1 枚の結果として取り出すための仕組み。
INNER JOIN は「両方に対応する行があるものだけ」、LEFT JOIN は「左側は全部残し、右側はあればくっつける(なければ NULL)」という違いがある。
JOIN したあとに WHERE で条件をかけることで、「どの行だけを残すか」をさらに絞り込む。LEFT JOIN と WHERE の組み合わせでは、NULL をどう扱うかで意味が変わるので注意が必要。
3 テーブル以上も、INNER JOIN / LEFT JOIN を重ねていくだけで書けるが、常に「どのテーブルとどのテーブルを、どのカギでつないでいるか」を意識することが大事。
