Python | DB・SQL:JOIN

Python
スポンサーリンク

概要(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 でいうと、
UserOrderForeignKey(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;
SQL

JOIN は「何回でも書ける」ので、
必要なだけテーブルをつなげていくイメージです。

ここでも大事なのは、
それぞれの 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 を重ねていくだけで書けるが、常に「どのテーブルとどのテーブルを、どのカギでつないでいるか」を意識することが大事。

タイトルとURLをコピーしました