Loading

ROUTE

ルートゼロの
アクティビティ

LEFT JOINで行が消える?WHERE条件の見直し方

1

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へ置いた場合との結果を比較してみてください。

カジュアル面談はこちら

RANKINGranking-icon

すべてを開示している会社です

ポジティブな面もネガティブな面も、包み隠さず誠実にお答えします。
気になることがあれば、面談の際にすべてオープンにお話します。