LEFT JOINを使ったのに左側テーブルの行が消える場合、右側テーブルの条件を`WHERE`に書いていないか確認します。LEFT JOINで一致しなかった行は右側の列がNULLになります。その後に`WHERE right.status = 'ACTIVE'`のような条件をかけると、NULLの行は条件を満たさず除外されます。「左側の行は残し、ACTIVEの右側行だけ結合したい」なら、その条件を`ON`側へ置くのが基本です。
まず結論:右表の条件をWHEREに置くと未一致行が消える
| 書き方 | 左表の未一致行 | 意味 |
|---|---|---|
| LEFT JOIN … ON … WHERE right.status = 'ACTIVE' | 消える | JOIN後の結果からACTIVE以外を除外する |
| LEFT JOIN … ON … AND right.status = 'ACTIVE' | 残る | ACTIVEの右行だけを結合対象にする |
| INNER JOIN … ON … AND right.status = 'ACTIVE' | 消える | 一致するACTIVE行だけが必要な場合 |
PostgreSQLの現行ドキュメントでも、LEFT OUTER JOINは一致しない左側行を右列NULLで追加し、その後WHEREはFROM句で作られた結果へ適用されます。WHEREの条件結果がfalseまたはNULLなら、その行は除外されます。
よくある失敗:右テーブルの条件をWHEREへ書く
ユーザー一覧に、ACTIVEな注文があれば結合したい例を考えます。次のSQLでは、注文がないユーザーやACTIVEではない注文しかないユーザーが結果から消えます。
SELECT
u.id,
u.name,
o.id AS order_id
FROM users u
LEFT JOIN orders o
ON o.user_id = u.id
WHERE o.status = 'ACTIVE';
LEFT JOINで未一致だったユーザーの`o.status`はNULLです。`WHERE o.status = 'ACTIVE'`はその行をtrueにできないため、WHERE段階で取り除かれます。結果だけを見るとINNER JOINに近い状態になります。
左側の行を残したいなら右表条件をONへ移す
目的が「全ユーザーを残しつつ、ACTIVEな注文だけを結合する」なら、右テーブルへかける条件をJOIN条件へ含めます。
SELECT
u.id,
u.name,
o.id AS order_id
FROM users u
LEFT JOIN orders o
ON o.user_id = u.id
AND o.status = 'ACTIVE';
この場合、ACTIVEな注文がないユーザーも左側行として残り、`order_id`など右側列がNULLになります。条件をONへ移すのは見た目の整理ではなく、外部結合でどの行を「一致」とみなすかを変える操作です。
なぜONとWHEREで結果が変わるのか
LEFT JOINは、まずON条件で左右の行を照合します。一致する右行がなければ、左行に右側NULLを付けて結果へ残します。その後、WHEREはJOIN後の行を絞り込みます。
- ON:右側のどの行を結合相手として認めるかを決める
- LEFT JOIN:一致しない左行を右側NULLで補う
- WHERE:JOIN後にできた各行を残すか落とすかを決める
そのため外部結合では、ON条件とWHERE条件は常に置き換え可能ではありません。PostgreSQLのドキュメントも、outer joinのON/USING条件はWHERE条件と等価ではないと明記しています。
用語解説:NULL補完行
LEFT JOINで右側に一致する行がないとき、左側の行を残すために右側列へNULLを入れて作られる結果行です。
すべてのWHERE条件をONへ移せばよいわけではない
条件の置き場所は「何を残したいか」で決めます。左テーブル自体を絞りたい条件までONへ移すと、意図と違う結果になることがあります。
| 目的 | 置き場所の例 |
|---|---|
| 東京のユーザーだけを対象にする | WHERE u.prefecture = 'Tokyo' |
| ACTIVEな注文だけを結合する | ON … AND o.status = 'ACTIVE' |
| ACTIVEな注文を持つユーザーだけ取得する | INNER JOINまたはLEFT JOIN + WHEREを意図的に使う |
| 注文を一件も持たないユーザーを探す | LEFT JOIN後にWHERE o.id IS NULL |
特に「右側条件があるWHEREは全部悪い」と覚えるのは危険です。未一致行を残したいのか、それとも右側条件を満たす行だけ欲しいのかを先に決めます。
注文がない行を探すならIS NULLを使う
LEFT JOINのNULL補完を意図的に利用すると、「対応する右側行が存在しない左側行」を抽出できます。
SELECT
u.id,
u.name
FROM users u
LEFT JOIN orders o
ON o.user_id = u.id
WHERE o.id IS NULL;
このパターンは「注文がないユーザー」のような未一致データを探すときに使えます。ここではWHEREでNULL行を残すこと自体が目的なので、WHEREの使用は意図どおりです。
切り分けは右側列をWHEREで参照していないか確認する
- LEFT JOINの右側テーブルの列をWHEREで絞っていないか確認する
- その条件が「結合相手の条件」なのか「最終結果の絞り込み」なのか言語化する
- 左側の未一致行を残したいなら右側条件をONへ移す
- 一致行だけ必要ならINNER JOINの方が意図を伝えやすいか検討する
- 未一致行を探す目的ならWHERE right.id IS NULLを検討する
(INNER・LEFT・RIGHTなどJOIN自体の基本を先に整理したい場合については『SQL JOINの基本から実践まで|INNER・LEFT・RIGHT・OUTER JOINの違いをわかりやすく解説』をご参照ください)
デバッグ時は、いったんWHEREを外して右側列がNULLになっている行を確認すると、JOINの段階で消えているのか、WHEREの段階で消えているのかを切り分けやすくなります。
よくある質問
LEFT JOINにWHEREを書いたら必ずINNER JOINと同じになりますか?
必ずではありません。右側列へNULLを許さない条件をWHEREに置くと未一致行が落ち、INNER JOINに近い結果になることがあります。WHEREの条件内容によって結果は変わります。
右テーブルの条件は全部ONに書くべきですか?
いいえ。左側の未一致行を残したまま結合相手を制限したい条件はONが適しますが、JOIN後の最終結果を絞る条件はWHEREが適します。目的で分けます。
注文がないユーザーだけ取得したい場合はどうしますか?
LEFT JOINした後、右側の識別子など確実に非NULLである列に対して`WHERE o.id IS NULL`とする方法があります。
LEFT JOINで想定より行数が減ったら、まずWHERE句の中に右テーブルの列がないか確認し、その条件をONへ置いた場合との結果を比較してみてください。