こんにちは、かつコーチです。
前回はINNER JOINで「一致する行だけ」を取得する方法を解説しました。
今回は、「一致しない行も含めて取得したい」ときに使うLEFT JOINとRIGHT JOINを解説します。
「一度も注文したことがない会員も含めて全員リストアップしたい」といった要件で必須の知識です。
LEFT JOIN・RIGHT JOINとは?
INNER JOINとの決定的な違い
INNER JOINは、結合条件に一致する行だけを返します。
一方、LEFT JOINとRIGHT JOINは、一致しない行も残す「外部結合」と呼ばれる結合方法です。
LEFT JOIN:FROM句で先に書いた(左側の)テーブルの行を全部残すRIGHT JOIN:JOIN句で指定した(右側の)テーブルの行を全部残す
一致する相手が見つからなかった側のカラムには、NULLが入ります。
なぜ外部結合が必要なのか
users(会員)とorders(注文)の例で考えます。
「一度も注文していない会員も含めて、全会員の注文状況を一覧にしたい」という要件があったとします。INNER JOINでは、注文実績のない会員は結果から消えてしまうため、この要件を満たせません。LEFT JOINを使うことで、注文がない会員も「注文なし(NULL)」として一覧に残せます。
基本の書き方
LEFT JOINで「全会員+注文があれば注文情報」を取得する
SELECT u.name, o.product_name
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;
結果は次のようになります。
name | product_name
田中 | ノートPC
田中 | マウス
山田 | キーボード
佐藤 | NULL ← 一度も注文がない会員もNULLで表示される
FROM users uが左側のテーブルなので、usersの全行が必ず結果に残ります。orders側に一致するデータがなければ、product_nameはNULLになります。
RIGHT JOINの書き方
RIGHT JOINはLEFT JOINのテーブルの左右を逆にしたものです。
SELECT u.name, o.product_name
FROM orders o
RIGHT JOIN users u ON o.user_id = u.id;
上のクエリは、先ほどのLEFT JOINの例と全く同じ結果を返します。
実務ではRIGHT JOINはあまり使われません。FROMとJOINの順番を入れ替えればLEFT JOINだけで同じことを表現できるため、可読性の観点からLEFT JOINに統一する現場がほとんどです。
一致しなかった行だけを取り出すテクニック
LEFT JOINとWHERE ... IS NULLを組み合わせると、「関連データが1件もない行」だけを抽出できます。
SELECT u.name
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.id IS NULL;
「一度も注文したことがない会員」だけをリストアップする、休眠会員の抽出などによく使われるパターンです。
つまずきやすい設定・注意点
LEFT JOINの後にWHEREを普通に書くとINNER JOIN扱いになる
これは中級者でも意外とハマる落とし穴です。LEFT JOINで結合しても、右側テーブルのカラムに対してWHEREで条件を指定すると、NULLの行が除外されてしまい、結果的にINNER JOINと同じ挙動になってしまうことがあります。
私が実際に経験したケースです。
「全会員の、今月の注文状況(注文がなければ0件)を一覧にする」というレポートを作る際、次のようなクエリを書きました。
❌ Before(WHEREでNULLの会員が消えてしまう)
SELECT u.name, o.product_name
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.order_date >= '2026-08-01';
このクエリは、「8月に注文がある会員」しか残らず、LEFT JOINにした意味がなくなってしまいます。o.order_date >= '2026-08-01'という条件は、o.order_dateがNULL(=注文がない)の行では成立せず、除外されてしまうためです。
✅ After(結合条件(ON句)の中に日付条件を含める)
SELECT u.name, o.product_name
FROM users u
LEFT JOIN orders o
ON u.id = o.user_id AND o.order_date >= '2026-08-01';
このようにON句の中に条件を移動させることで、「8月の注文がなくても会員自体は結果に残す」という本来の意図を実現できます。
このバグに気づいたのは、レポートの会員数が実際の総会員数より少ないと指摘を受けたときでした。
「LEFT JOINしたのに件数が減る」という症状が出たら、まずWHERE句に右側テーブルの条件が紛れ込んでいないか確認するとよいでしょう。
NULLになったカラムの後続処理を忘れない
外部結合で生じたNULLは、その後の集計や表示処理でも意識する必要があります。
前回解説したCOALESCEなどを使って、NULLを「0件」「未購入」といった分かりやすい表示に変換すると親切です。
SELECT u.name, COUNT(o.id) AS order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name;
COUNT(o.id)はNULLを数えないため、注文がない会員は自動的に0件として集計されます。
応用・一歩先の使い方
複数のLEFT JOINを重ねる
users・orders・reviewsのように3つ以上のテーブルを扱う場合、LEFT JOINを複数連結できます。
SELECT u.name, o.product_name, r.rating
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
LEFT JOIN reviews r ON o.id = r.order_id;
「注文はあるがレビューはまだ」「注文自体がない」といった段階的な欠損データも、正しく表現できます。
FULL OUTER JOINが必要な場合の代替
標準SQLには、両側の一致しない行を残すFULL OUTER JOINがありますが、MySQLはこれをサポートしていません。
MySQLで同等の結果が必要な場合は、LEFT JOINの結果とRIGHT JOINの結果をUNIONで結合して代用します。UNIONの使い方は、このシリーズの後の記事で解説します。
まとめ
この記事のポイント
LEFT JOINは左側テーブルの全行を残し、一致しない場合は右側がNULLになるRIGHT JOINは右側テーブルを基準にするが、実務ではLEFT JOINへの統一が一般的LEFT JOINの後にWHEREで右側テーブルの条件を書くと、意図せずINNER JOIN相当になってしまう- 右側テーブルへの絞り込み条件は、WHEREではなくON句に含める
次に読むべき記事
- SQL INNER JOINで複数テーブルを結合する
- SQL 複数テーブル JOIN
- SQL UNION UNION ALL 違い
タグ: SQL, 中級者向け, JOIN