SQL JOINの種類と使い分け
JOINは「2つの表をどう重ねるか」の指定です。種類はINNER/LEFT/RIGHT/FULL/CROSSの5つですが、 実務で迷うポイントは「残したい行はどちらの表か」と「条件をONとWHEREのどちらに書くか」の2点に集約されます。
This guide explains SQL JOIN types with concrete result sets, and the classic pitfall where a WHERE condition silently turns a LEFT JOIN into an INNER JOIN.
TL;DR
- INNER: 両方にある行だけ。LEFT: 左は全部残す(右が無ければNULL)
- LEFT JOINの右表条件はONに書く。WHEREに書くとNULL行が消えてINNERと同じ結果になる
- 1対多の結合は行が増える。集約してから結合するか、結合後にGROUP BYする
1. サンプルデータ / Sample data
以下の2表で全JOINの結果を比較します。
| users(左表) | orders(右表) | ||
|---|---|---|---|
| id | name | user_id | item |
| 1 | 佐藤 | 1 | 本 |
| 2 | 鈴木 | 1 | ペン |
| 3 | 高橋 | 4 | 机 |
2. 種類別の結果 / Result by JOIN type
| JOIN | 結果(name, item)/ Result | 使いどころ / When to use |
|---|---|---|
INNER JOIN | 佐藤-本, 佐藤-ペン | 両方に存在するデータだけ欲しい |
LEFT JOIN | 佐藤-本, 佐藤-ペン, 鈴木-NULL, 高橋-NULL | 「注文の無いユーザー」も含めた一覧 |
RIGHT JOIN | 佐藤-本, 佐藤-ペン, NULL-机 | 通常は表を入れ替えてLEFTで書く方が読みやすい |
FULL OUTER JOIN | 上記すべて(鈴木-NULL, NULL-机 を含む) | 両側の突合・差分洗い出し(MySQL非対応) |
CROSS JOIN | 3×3=9行の全組合せ | 組合せ生成。意図せず書くと行数爆発 |
3. 最大の罠: ONとWHEREの違い / The ON vs WHERE pitfall
「キャンセル以外の注文を、注文ゼロのユーザーも含めて一覧したい」とき、次のSQLは間違いです。
-- NG: 注文ゼロのユーザー(item IS NULL)がWHEREで消え、INNER JOINと同じ結果に
SELECT u.name, o.item
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE o.status <> 'cancelled';
-- OK: 右表への条件はONに書く
SELECT u.name, o.item
FROM users u
LEFT JOIN orders o ON o.user_id = u.id AND o.status <> 'cancelled';ONは結合の最中に評価され、マッチしなくても左の行はNULL付きで残ります。 WHEREは結合の後に評価されるため、NULLになった行が条件で落ちます。 「LEFT JOINしたのに件数がINNERと同じ」ときはまずここを疑ってください。
ON filters while matching (unmatched left rows survive with NULLs); WHERE filters after the join (NULL rows get dropped). Put right-table conditions in ON to keep the LEFT JOIN semantics.
4. その他の頻出ポイント / Other common points
- 1対多で行が増える: 集計を伴うときはサブクエリで右表を
GROUP BYしてから結合すると安全 - MySQLにFULL OUTER JOINは無い:
LEFT JOIN ... UNION ... RIGHT JOINで代用 - カンマ結合(
FROM a, b WHERE ...)は旧記法: CROSS JOINと同じ意味。明示的なJOIN構文に書き換える - NULL同士は結合されない:
NULL = NULLは真にならないため、キーにNULLを含む行はINNERでもLEFTでも対象外
JOINが3つ以上連なるSQLは、インデントが揃っているだけで読み違いが激減します。SQL整形ツールに貼り付ければJOIN句単位で整形できます。
5. English summary
INNER JOIN keeps only matching rows; LEFT JOIN keeps every left row and fills missing right columns with NULL; RIGHT JOIN is the mirror (usually rewritten as LEFT for readability); FULL OUTER JOIN keeps both sides (not supported by MySQL — emulate with UNION); CROSS JOIN produces every combination. The classic bug: putting a right-table condition in WHERE after a LEFT JOIN filters out the NULL rows and silently degrades it to an INNER JOIN — put that condition in ON instead. With one-to-many keys the row count multiplies, so aggregate before joining when you need stable counts.
関連ツール / Related tools
- SQL Formatter - 複数JOINのSQLを読みやすく整形
- Fake Data Generator - JOIN検証用のテストデータ生成
- CSV ⇔ JSON Converter