sql left join完全ガイド:基本構文からNULL・集計・最適化まで

この記事では、SQLのJOINを基礎から実践まで整理し、INNER/LEFT/RIGHT/OUTER JOINの違い、NULLを含む結合の考え方、部署別の社員数集計などの具体例で学べます。結合結果が想定とズレる原因や、SQL Serverの結合方式(ループ・マージ・ハッシュ)も把握でき、正しく速いクエリ作成の悩みを解決します。

目次

SQLのLEFT JOINとは何か(外部結合の基本)

sql+leftjoin+database

SQLのLEFT JOIN(左外部結合)は、「左側(FROM句に書いたテーブル)の行をすべて残しつつ、右側テーブルは結合条件に一致した行だけを付ける」ための結合です。右側に一致する行が存在しない場合でも、左側の行は結果に出力され、そのとき右側由来の列はNULLになります。

この性質により、LEFT JOINは「紐づくデータが無い行も含めて一覧表示したい」「対応関係が欠けているデータを見つけたい」といった場面で欠かせません。まずは、内部結合(INNER JOIN)との違い、そしてLEFT JOINで“何が残り”“どこがNULLになるか”を押さえることが重要です。

INNER JOIN(内部結合)との違い

INNER JOINは「両方のテーブルで結合条件に一致した行だけ」を返します。一方、sql left joinは「一致しなくても左側は残す」という点が決定的に異なります。

言い換えると、INNER JOINは“共通部分(積集合)”、LEFT JOINは“左側を基準にした拡張”です。結果セットの行数の傾向も次のように変わります。

  • INNER JOIN:一致しない左側の行は落ちる(結果に出ない)
  • LEFT JOIN:一致しない左側の行も残る(右側の列がNULLになる)

そのため、LEFT JOINを使うと「欠損があっても左テーブルの母集団は維持される」一方、INNER JOINだと「紐づかない行が消える」ため、集計や一覧作成の前提が変わることがあります。どちらが正しいかは目的次第で、LEFT JOINは“落としたくない行がある”ときに選ぶ結合です。

LEFT JOINで「残る行」と「NULLになる列」を理解する

LEFT JOINを正しく使う鍵は、結果において「どの行が必ず残るのか」と「どの列がNULLになり得るのか」を明確に理解することです。LEFT JOINでは、左テーブルの各行に対して右テーブルがマッチするかを探し、見つかれば右側の列が埋まり、見つからなければ右側の列がNULLとして返されます。

重要なのは、NULLになるのは“右テーブル由来の列”であり、左テーブルの行自体は消えないという点です。たとえば次のようなイメージです。

左テーブルの行右テーブルが一致結果
存在するある左の行 + 右の列が値を持つ
存在するない左の行 + 右の列がNULLになる

また、右テーブル側に結合キーが同じ行が複数存在する場合は、左側1行に対して右側の一致行数だけ結果が増えます。LEFT JOINは「左側を必ず残す」結合ですが、「必ず1行にまとまる」結合ではありません。結合キーの設計やデータの一意性(ユニーク性)によって、結果の行数が変化し得る点を意識しておくと、思わぬ行増加を避けやすくなります。

NULL値とLEFT JOINの挙動(結合条件の注意点)

sql left joinを扱う際、NULLは特別な存在です。SQLの比較では、NULLは「不明」を表すため、一般的な比較演算(= など)で一致判定されません。これがLEFT JOINの結合条件(ON句)に影響し、想定外に“マッチしない扱い”となることがあります。

具体的には、結合キーにNULLが入っていると、ON句で「左.キー = 右.キー」と書いてもNULL同士は一致になりません。その結果、該当行は「右側が見つからない」扱いになり、右側の列はNULLとして返ります。これはLEFT JOINの仕様というより、SQLにおけるNULL比較の基本ルールによるものです。

  • 結合キーがNULLの場合:結合条件が成立しにくく、右側はNULLになりやすい
  • 右側の列がNULLになる理由:右に行が無い/あっても結合条件が成立しない、のどちらもあり得る

また、LEFT JOINの結果で右側列がNULLになっているとき、「本当に右側に対応行が存在しない」のか、「対応行はあるが結合条件が成立しない(NULL比較や条件式の影響)」のかは切り分けが必要です。“NULLが出た=相手テーブルに行が無い”と即断すると、原因調査を誤ることがあります。

LEFT JOINを安定して使うには、結合条件に使う列にNULLが入り得るかを把握し、NULLが含まれるデータがどう扱われるべきか(結合させたいのか、させないのか)を仕様として決めておくことが大切です。

LEFT JOINの書き方と基本構文

sql+leftjoin+optimization

sql left joinは、「左側(FROM句に書いたテーブル)の行を必ず残しつつ、右側(JOINするテーブル)に一致する行があれば列を埋める」ための結合です。書き方自体はシンプルですが、条件の置き場所(ONかWHEREか)や、結合キーの指定、複数テーブル結合の順序で結果が変わりやすいのが特徴です。ここでは、LEFT JOINを正しく書くための基本構文と、実務で混乱しやすいポイントに絞って整理します。

SELECT
  列リスト
FROM
  left_table
LEFT JOIN right_table
  ON 結合条件;

ポイントは次の2つです。

  • 「LEFT JOINの条件」は基本的にON句に書く(右テーブル側の絞り込みを含む)
  • WHERE句は「結合後の結果セット」に対する絞り込みになる

ON句とWHERE句の違い(結果が変わる典型パターン)

sql left joinで最もつまずきやすいのが、右テーブルの条件をON句に書くかWHERE句に書くかで結果が変わる点です。結論として、LEFT JOINで「左側を残す」意図を守りたいなら、右テーブルに関する条件はON句へ寄せるのが基本です。

違いを理解するために、よくある「注文(左)と入金(右)」の例を考えます。

-- 例:orders(注文)を必ず出し、payments(入金)があれば付けたい
SELECT
  o.order_id,
  p.paid_at
FROM orders o
LEFT JOIN payments p
  ON p.order_id = o.order_id;

この状態で「入金が確定したものだけ payments から拾いたい(例:p.status = ‘CONFIRMED’)」という条件を追加するとき、WHEREに書くとLEFT JOINの意味が崩れる典型パターンが起きます。

-- NGになりやすい:WHEREに右テーブル条件を書く
SELECT
  o.order_id,
  p.paid_at
FROM orders o
LEFT JOIN payments p
  ON p.order_id = o.order_id
WHERE p.status = 'CONFIRMED';

この書き方だと、入金行が存在しない注文は p.status が NULL になり、WHERE句で落ちます。その結果、「左側を残す」はずのLEFT JOINが、実質的にINNER JOINのような結果になりやすいです。

左側を残したまま、右側の行だけ条件で絞りたい場合は、次のようにON句へ移します。

-- 推奨:ONに右テーブルの条件を書く(左は残る)
SELECT
  o.order_id,
  p.paid_at
FROM orders o
LEFT JOIN payments p
  ON p.order_id = o.order_id
 AND p.status = 'CONFIRMED';

この場合、入金が存在しない注文は残り、p.* はNULLになります。「左を残しつつ、右の一致条件を厳しくする」というLEFT JOINらしい挙動になります。

なお、WHERE句を使うべき場面もあります。たとえば「左テーブル側の条件(期間やステータスなど)」で絞るのは自然です。

SELECT
  o.order_id,
  p.paid_at
FROM orders o
LEFT JOIN payments p
  ON p.order_id = o.order_id
WHERE o.created_at >= '2026-01-01';

エイリアスと結合キーの指定方法

LEFT JOINを読みやすく・安全に書くには、テーブルエイリアス結合キー(JOINキー)を明確にするのが重要です。特に列名が被りやすい(id、created_at など)環境では、曖昧な列参照がバグやエラーの原因になります。

基本形は次の通りです。

SELECT
  o.order_id,
  o.customer_id,
  c.customer_name
FROM orders AS o
LEFT JOIN customers AS c
  ON c.customer_id = o.customer_id;
  • エイリアスは短く意味が通るもの(例:orders→o、customers→c)にする
  • SELECT句は「どのテーブルの列か」を エイリアス.列 で明示する
  • ON句は「右テーブルのキー = 左テーブルのキー」の形で揃えると読みやすい

また、LEFT JOINは「左を基準に右をくっつける」ため、FROMに置いたテーブルが基準になります。どちらが基準かを曖昧にしないためにも、FROMLEFT JOIN の位置関係を意識して書くことが大切です。

複数テーブルをLEFT JOINでつなぐ手順

複数テーブルをsql left joinでつなぐ場合は、「基準(左)→追加(右)」を段階的に増やすのがコツです。LEFT JOINは結合を重ねるほど「どの時点でNULLになったのか」が重要になるため、順番とON句の書き方を統一しておくと、意図しない欠損や参照ミスを避けられます。

基本手順は次の通りです。

  1. まず基準となるテーブルをFROMに置く
  2. 次に「直接ひもづく」テーブルをLEFT JOINする(結合キーをONで明示)
  3. さらにテーブルを増やす場合は、どのテーブルにひもづくかを決め、ON句でキーをつなぐ

例として、注文(orders)を基準に、顧客(customers)と入金(payments)を付ける形は次のようになります。

SELECT
  o.order_id,
  o.customer_id,
  c.customer_name,
  p.paid_at
FROM orders o
LEFT JOIN customers c
  ON c.customer_id = o.customer_id
LEFT JOIN payments p
  ON p.order_id = o.order_id;

複数LEFT JOINで混乱しやすい点として、後続のJOINで参照するキーを「どの段階のテーブルに結びつけるか」があります。たとえば、3つ目のテーブルが2つ目のテーブルにひもづくなら、ON句もそれに合わせます。

SELECT
  o.order_id,
  c.customer_name,
  ca.city
FROM orders o
LEFT JOIN customers c
  ON c.customer_id = o.customer_id
LEFT JOIN customer_addresses ca
  ON ca.customer_id = c.customer_id;

このように、LEFT JOINを増やすときは「結合の起点となるテーブル(どれにぶら下がるか)」を毎回固定し、ON句の左辺・右辺を揃えて書くと、構文としても意図としても追いやすくなります。

LEFT JOINの代表的な使い方(実務でよくあるパターン)

sql+leftjoin+database

sql left joinは「左側(基準側)の行を必ず残す」結合です。実務では、この性質を利用して「欠損の見える化」「存在しないデータの抽出」「0件を含めた集計」など、データ品質確認からレポーティングまで幅広く使われます。ここでは現場で頻出する代表パターンを、すぐ流用できるSQL例と合わせて整理します。

マスタに対して明細が欠けても一覧を出す(欠損の可視化)

典型例は「マスタ(一覧として必ず出したい)」に対して「明細(存在しない場合がある)」をぶら下げるケースです。たとえば商品マスタは全件出しつつ、当月の売上明細が無い商品も一覧に出すことで、未販売やデータ連携漏れを可視化できます。

例:商品マスタを基準に、当月の売上明細を付与する(売上が無い商品も残る)

SELECT
  p.product_id,
  p.product_name,
  s.sales_date,
  s.amount
FROM products AS p
LEFT JOIN sales AS s
  ON s.product_id = p.product_id
 AND s.sales_date >= DATE '2026-01-01'
 AND s.sales_date <  DATE '2026-02-01'
ORDER BY p.product_id;

ポイントは「欠けても一覧を出す」ために、基準にしたいテーブル(この例ではproducts)をFROMに置くことです。こうすると、salesが無い商品でも行自体は残り、売上側の列(sales_date/amount)がNULLになります。NULLが出る行=明細が欠けている候補、と判断しやすくなります。

未存在データの抽出(LEFT JOIN+NULL判定)

sql left joinは「存在しないものを見つける」用途でも非常に強力です。左側に“あるはずの集合”を置き、右側に“実際に存在する集合”を置くと、結合できなかった行は右側の列がNULLになります。これを利用して未登録・未設定・未提出などを抽出します。

例:全従業員のうち、今月の勤怠レコードが存在しない人を抽出する

SELECT
  e.employee_id,
  e.employee_name
FROM employees AS e
LEFT JOIN attendances AS a
  ON a.employee_id = e.employee_id
 AND a.work_date >= DATE '2026-01-01'
 AND a.work_date <  DATE '2026-02-01'
WHERE a.employee_id IS NULL
ORDER BY e.employee_id;

このパターンは「未存在の抽出(anti join)」としてよく使われます。WHERE句で右側テーブルのキー列(例ではa.employee_id)をIS NULL判定することで、結合に失敗した行だけを残せます。

  • マスタに存在するのに、設定テーブルに無い(権限・契約・紐付け漏れ)
  • 予定はあるのに、実績が無い(未提出・未入力)
  • 配布対象にいるのに、配布履歴が無い(未配布)

集計での活用(部署別などの件数・合計を出す)

集計レポートでは「0件の部署も表示したい」が頻出要件です。INNER JOINだと明細が無い部署は行ごと消えますが、sql left joinなら部署マスタを基準に残せるため、0件を含む一覧を作れます。

例:部署マスタを基準に、当月の申請件数と申請金額合計を出す

SELECT
  d.dept_id,
  d.dept_name,
  COUNT(a.application_id) AS application_count,
  COALESCE(SUM(a.amount), 0) AS total_amount
FROM departments AS d
LEFT JOIN applications AS a
  ON a.dept_id = d.dept_id
 AND a.applied_at >= TIMESTAMP '2026-01-01 00:00:00'
 AND a.applied_at <  TIMESTAMP '2026-02-01 00:00:00'
GROUP BY
  d.dept_id,
  d.dept_name
ORDER BY d.dept_id;

COUNTはNULLを数えないため、明細が無い部署では0になりやすい一方、SUMはNULLになり得るため、COALESCEで0に寄せるのが実務では定番です。

集計結果が0件になる行を残す方法

「0件も出す」ための基本方針は、“残したい軸(部署・商品・店舗など)のマスタを左側に置く”ことです。さらに、期間条件やステータス条件などの“明細側の条件”は、LEFT JOINのON句内に入れると安全に0件行を残しやすくなります。

  • 基準(必ず出したい)=マスタ:FROMに置く
  • 任意(無いことがある)=明細:LEFT JOINする
  • 明細側の期間・区分条件:ON句に書く(0件部署を落としにくい)
  • SUMのNULL対策:COALESCE(SUM(…), 0)

例:売上の無い店舗も含め、店舗別売上合計を出す

SELECT
  s.store_id,
  s.store_name,
  COALESCE(SUM(sl.amount), 0) AS sales_total
FROM stores AS s
LEFT JOIN sales AS sl
  ON sl.store_id = s.store_id
 AND sl.sales_date >= DATE '2026-01-01'
 AND sl.sales_date <  DATE '2026-02-01'
GROUP BY
  s.store_id,
  s.store_name
ORDER BY s.store_id;

GROUP BYとLEFT JOINの組み合わせの注意点

LEFT JOINで集計する際は、意図せず結果が変わる落とし穴がいくつかあります。特に多いのが「WHERE句で右側テーブルの条件を書いてしまい、実質INNER JOINになって0件行が消える」「COUNTの対象列を誤って期待とズレる」などです。

  • 右側テーブルの条件をWHERE句に書くと、0件行が落ちやすい(必要ならON句へ)
  • COUNT(*)は左側行自体を数えるため、明細無しでも1になり得る。明細件数ならCOUNT(右側の主キー)が無難
  • SUMは明細無しでNULLになり得るため、表示用にはCOALESCEで0にする
  • 右側が多対多・重複を含むと合計が膨らむ(集計前に明細を適切に絞る/粒度を揃える)

例:部署別の明細件数を数えるなら、COUNT(*)ではなくCOUNT(明細キー)を使う

SELECT
  d.dept_id,
  d.dept_name,
  COUNT(a.application_id) AS application_count
FROM departments AS d
LEFT JOIN applications AS a
  ON a.dept_id = d.dept_id
GROUP BY
  d.dept_id,
  d.dept_name;

LEFT JOINと他のJOINの比較(使い分け)

sql+leftjoin+join

SQLの結合は「どちらの表を基準に行を残したいか」「一致しない行をどう扱いたいか」「行数が増える可能性を許容できるか」で使い分けます。特にsql left joinは「左(FROM側)の行を必ず残す」結合として定番ですが、似た名前のJOINと混同すると結果が変わります。この章ではLEFT JOINを軸に、RIGHT JOIN・FULL OUTER JOIN・CROSS JOINとの違いを整理し、実務で迷いにくい判断基準を示します。

RIGHT JOINとの違いと置き換え方法

RIGHT JOINは「右(JOIN側)の行を必ず残す」外部結合です。LEFT JOINが“左基準”であるのに対し、RIGHT JOINは“右基準”という点だけが本質的な違いです。つまり、テーブルの並び順を入れ替えれば同じ結果をLEFT JOINで表現できます。

RIGHT JOINはDBやチームのコーディング規約で敬遠されることもあり、可読性の観点からLEFT JOINへ統一する運用もよくあります。置き換えの要点は「左右のテーブルを入れ替え、選択する列もそれに合わせる」ことです。

-- RIGHT JOIN(右のBを必ず残す)
SELECT
  A.id,
  B.id AS b_id
FROM A
RIGHT JOIN B
  ON A.key = B.key;

-- LEFT JOINに置き換え(左にBを持ってくる)
SELECT
  A.id,
  B.id AS b_id
FROM B
LEFT JOIN A
  ON A.key = B.key;

どちらを使うべきか迷ったら、「FROMに書いた表が“基準(必ず残る)”」になるようにLEFT JOINで統一すると、クエリを読む人が意図を追いやすくなります。

FULL OUTER JOINの考え方(対応DBと代替手段)

FULL OUTER JOINは「左右どちらの表の行もすべて残す」結合です。一致する行は1行にまとまり、一致しない行は相手側の列がNULLになります。LEFT JOINが「左は残す/右は欠ける可能性がある」のに対し、FULL OUTER JOINは「左も右も欠ける可能性があるが、行自体は残す」というイメージです。

対応状況はDBによって異なります。例えばPostgreSQLやSQL Server、OracleなどはFULL OUTER JOINをサポートしますが、MySQLはFULL OUTER JOINを直接サポートしていません(一般的な利用形態では代替が必要です)。

MySQLなどでの代替として代表的なのが、LEFT JOINとRIGHT JOIN(または左右入れ替えたLEFT JOIN)をUNIONで合成する方法です。重複行の扱いをどうするかでUNION(重複排除)とUNION ALL(重複保持)を使い分けます。

-- FULL OUTER JOINの代替(概念例)
-- 1) 左を残す(LEFT JOIN)
SELECT
  A.key,
  A.col_a,
  B.col_b
FROM A
LEFT JOIN B
  ON A.key = B.key

UNION

-- 2) 右を残す(Aに存在しないBの行を拾う)
SELECT
  B.key,
  A.col_a,
  B.col_b
FROM B
LEFT JOIN A
  ON A.key = B.key
WHERE A.key IS NULL;

この代替手段では、同一キーが複数行ある場合(多対多など)に想定以上に行が増えることがあります。また、列の型やNULLの扱いで結果が揺れやすいので、FULL OUTER JOINの目的が「両側の欠損を一覧化したい」なのか「突合結果を完全に統合したい」なのかを先に明確にしておくのが重要です。

CROSS JOINとの違い(意図しない行増加を防ぐ)

CROSS JOINは結合条件を持たない結合で、左右の行のすべての組み合わせ(直積)を返します。LEFT JOINが「キー一致を前提に、合わない場合も左を残す」のに対し、CROSS JOINは「一致・不一致という概念がなく、全組み合わせを作る」ため、行数が爆発しやすいのが最大の特徴です。

例えば、Aが100行、Bが200行なら、CROSS JOINは20,000行になります。ここで「sql left joinを書いたつもりなのに行が増えた」という事故の原因として、以下がよくあります。

  • JOIN条件(ON句)の書き忘れ・誤記により、実質CROSS JOIN(またはそれに近い状態)になっている
  • 結合キーが一意でなく、片側(または両側)で複数行にマッチして組み合わせが増えている

CROSS JOIN自体は悪いものではなく、たとえば「日付カレンダー×商品マスタ」のように、全組み合わせを作ってから存在データをLEFT JOINで載せる設計で使うこともあります。ただし意図せずCROSS JOIN相当になると性能・件数ともに致命的になりがちです。対策としては、JOINを書く際に次を習慣化すると安全です。

  • LEFT JOINでは必ずONに結合キーを明記し、「どの列で一致させるか」を言語化できる状態にする
  • 結合前後で想定行数を見積もり、増える場合は「キーの重複」か「条件不足」を疑う
  • 必要に応じて事前に重複を解消(集約・DISTINCT・サブクエリ等)してから結合する

LEFT JOINでつまずきやすいポイントと対処法

sql+leftjoin+database

sql left joinは「左側テーブルの行を残す」という性質が便利な一方で、結合条件や絞り込み条件の書き方次第で結果が大きく変わり、意図しない重複や欠損が起きやすいJOINでもあります。ここでは実務で特につまずきやすい4つの論点を取り上げ、よくある原因と対処法を整理します。

結合条件が不適切で行が増える(重複・多対多)

LEFT JOINで「行が増えた」「同じ左テーブルの行が何回も出る」という現象の多くは、結合条件が一意になっていないことが原因です。右テーブル側で結合キーが重複していると、左の1行に対して右の複数行がマッチし、結果セットが増殖します。さらに左右ともに重複があると多対多(many-to-many)になり、掛け算のように行数が膨れ上がります。

典型例は「ユーザー×注文」のように1対多が前提のテーブルを、明細行の粒度のまま結合してしまうケースです。期待が「ユーザー一覧に注文有無を付けたい」なのに、注文が複数あるユーザーが複数行に分裂してしまいます。

対処の基本は、結合前に右側の粒度を期待する形に整えることです。

  • 右テーブルが1対多なら、必要に応じて集約してから結合する(1ユーザー1行にする)
  • 重複が不要なら、DISTINCTではなく「なぜ重複するのか(キー設計・条件)」を先に見直す
  • 「最新1件だけ欲しい」などの場合は、右側で絞り込んだ結果をサブクエリ化してからLEFT JOINする
-- 例:ユーザー一覧に「注文回数」を付けたい(右側を集約してからLEFT JOIN)
SELECT
  u.user_id,
  u.name,
  COALESCE(o.order_count, 0) AS order_count
FROM users u
LEFT JOIN (
  SELECT user_id, COUNT(*) AS order_count
  FROM orders
  GROUP BY user_id
) o
ON o.user_id = u.user_id;

なお、行数が増えたときの切り分けとしては、まず「左テーブル単体の行数」と「結合後の行数」を比較し、増えた分だけ右側に複数マッチがあると疑うのが近道です。

フィルタ条件の置き場所で結果が変わる(ONに寄せる/WHEREで絞る)

sql left joinで最も混乱が起きやすいのが、右テーブルに関する条件をWHERE句に書くと、LEFT JOINなのに行が消える問題です。LEFT JOINは「マッチしない場合でも左の行を残す」ものですが、WHERE句で右テーブルの列に条件をかけると、NULLになった行が条件に合致せず落ちるため、結果的にINNER JOINのような挙動になります。

ポイントは次の使い分けです。

  • ON句:結合の成立条件(右側に「付けられる行だけ付ける」)。左側の行を残したい目的に相性が良い
  • WHERE句:結合後の結果セットを絞り込む(条件に合わない行は左側であっても落ちる)

例えば「全ユーザーを出しつつ、右側は有効な注文だけ付けたい」場合は、右側条件をONに寄せます。

-- 全ユーザーは残す。注文はstatus='PAID'のものだけ結合する。
SELECT
  u.user_id,
  o.order_id
FROM users u
LEFT JOIN orders o
  ON o.user_id = u.user_id
 AND o.status = 'PAID';

逆に「有効な注文が1件でもあるユーザーだけ出したい」ならWHEREで絞る(あるいはINNER JOINにする)意図になります。

-- 注文がないユーザー(oがNULL)はWHEREで落ちるため、結果的に残らない
SELECT
  u.user_id,
  o.order_id
FROM users u
LEFT JOIN orders o
  ON o.user_id = u.user_id
WHERE o.status = 'PAID';

この差は小さな書き方の違いに見えますが、分析や一覧表示で「欠損も含めて可視化したい」ケースでは致命的になりやすいので、「左側を残す目的か?それとも結果として絞りたいのか?」を先に決めてから条件の置き場所を選ぶのが安全です。

NULLの扱い(比較演算・COALESCEの活用)

LEFT JOINでは、右側がマッチしない行の列がNULLになります。このNULLが原因で、比較演算や集計・条件分岐が思った通りにならないことがあります。特に注意したいのは、NULLは「値がない」ため、= や <> の比較がTRUEにならない点です。

例えば、右側が存在しない行(NULL)を含む状態で、次の条件を書いた場合を考えます。

-- o.statusがNULLの行は 'PAID' と等しくも等しくなくもならない(UNKNOWN)
WHERE o.status <> 'PAID'

このとき、o.statusがNULLの行は条件に合致せず除外されることがあり、意図せず「結合できなかった行」が落ちます。NULLを含めて扱いたい場合は、目的に応じて明示的に書き分けます。

  • NULLも含めて「PAID以外」を取りたい:WHERE o.status IS NULL OR o.status <> 'PAID'
  • 表示上のNULLを別値に置き換えたい:COALESCE(o.status, 'NO_ORDER')
  • 数値計算でNULLを0として扱いたい:COALESCE(o.amount, 0)
SELECT
  u.user_id,
  COALESCE(o.amount, 0) AS amount,
  COALESCE(o.status, 'NO_ORDER') AS status_label
FROM users u
LEFT JOIN orders o
  ON o.user_id = u.user_id;

また、JOINキー自体にNULLが混ざる場合も注意が必要です。結合条件でt1.key = t2.keyとしていると、どちらかがNULLの行は結合されません。NULLを「同じ」とみなして結合したい要件は多くありませんが、もし必要ならDBの方言や設計見直しが絡むため、安易に「NULL同士を一致させる」発想で進めない方が安全です。

ORDER BYやLIMITを絡めたときの注意点

LEFT JOINにORDER BYやLIMIT(またはFETCH FIRST)を組み合わせると、意図しない並び・欠損が起きやすくなります。理由は、JOINで行数が増減したり、右側がNULLになったりすると、並び替えや「上位N件」の対象がズレるためです。

よくある落とし穴は次の2つです。

  • JOIN後にLIMITすると、左テーブルの「上位N行」ではなく、結合で増えた結果セットの「上位N行」になる
  • ORDER BYで右テーブル列を使うと、NULL行が先頭/末尾のどちらに来るかがDBや設定で異なることがある

例えば「ユーザーを作成日順に10人取り、その後に注文情報を付けたい」なら、ユーザー側の絞り込み(LIMIT)を先に確定させてからLEFT JOINするのが安全です。

-- 先にusersを10件に絞り、その10件に対して注文をLEFT JOINする
SELECT
  u.user_id,
  u.created_at,
  o.order_id
FROM (
  SELECT user_id, created_at
  FROM users
  ORDER BY created_at DESC
  LIMIT 10
) u
LEFT JOIN orders o
  ON o.user_id = u.user_id;

また、「右側の最新1件で並べたい」など、JOINしつつ順序を安定させたい場合は、右側を事前に「1件に確定」させる(例:最新行だけに絞ったサブクエリを作る)ことが重要です。そうしないと、同一ユーザーが複数行に分裂し、ORDER BYの結果がユーザー単位で見たときに不自然になります。

ORDER BYでNULLの並びが問題になる場合は、COALESCEで代替値を与えて並べるなど、NULLを前提に並びを設計すると期待値がブレにくくなります。

パフォーマンスの基礎(LEFT JOINが遅いときの見直し)

sql+leftjoin+performance

sql left join は「左側テーブルの行を残す」という性質上、結果行が増えやすく、結合相手の探索回数も増えるため、クエリが遅い原因になりがちです。特にデータ量が増えたタイミングや、検索条件(絞り込み)が変わったタイミングで急に遅くなるケースでは、インデックス設計と実行計画(どのJOIN方式で処理されるか)をセットで見直すのが最短ルートです。

インデックス設計の要点(結合キー/絞り込み列)

LEFT JOINの性能は、結合条件で使う列(結合キー)と、行数を減らす条件で使う列(絞り込み列)に適切なインデックスがあるかで大きく変わります。基本的に、左テーブルを起点に右テーブルを探しに行く局面が多いため、「右テーブル側の結合キー」が弱いと探索が重くなりやすいです。

  • 結合キー(ON句で一致させる列)にインデックス:右テーブルの結合キーにインデックスがあると、Nested Loopsなどで1行ずつ探しに行く場合のコストが下がります。
  • 絞り込み列(WHERE/ONでフィルタに使う列)も考慮:結合前に行数を減らせるとJOIN全体が軽くなります。フィルタ対象の列にインデックスがあるか確認します。
  • 複合インデックスの順序:一般に「等価条件で使う列(結合キー/検索キー)→追加で絞る列」の順で効きやすい傾向があります。LEFT JOINで右表を(結合キーで探し、さらに条件で絞る)なら、そのアクセスパターンに沿った複合化が候補です。
  • 選択度が低い列だけのインデックスは効きにくい:フラグ列など同値が多い列は単体だと効果が限定的です。結合キーや日付などと組み合わせて検討します。
  • インデックスを増やしすぎる副作用:INSERT/UPDATE/DELETEのコスト増、ストレージ増につながるため、実行計画で効果が出るものに絞ります。

ポイントは「sql left join の右側テーブルをどう探しているか」を想像し、その探索がインデックスで短絡できる形に寄せることです。

実行計画で確認すべきポイント(JOIN方式の理解)

LEFT JOINが遅いときは、まず実行計画で「どのJOIN方式(Nested Loops / Merge Join / Hash Joinなど)になっているか」と「どのテーブルが入力(外側)で、どのテーブルが探索対象(内側)か」を確認します。ここが分かると、改善策(インデックス追加、絞り込みの位置調整、統計情報の更新など)の方向性が具体化します。

  • 推定行数(Estimated Rows)と実績行数(Actual Rows)が大きく乖離していないか
  • 右テーブル側がフルスキャン(全件走査)になっていないか
  • ソート(Sort)が発生していないか(Merge Joinの前処理など)
  • ハッシュテーブル作成のメモリ不足や、ディスクへの退避が起きていないか(Hash Join)

ループ結合(Nested Loops)の特徴

Nested Loopsは、外側(多くは左側)から1行ずつ取り出し、内側(多くは右側)を条件に合う行を探しに行く方式です。少量の外側行に対して、内側がインデックスで素早く引ける場合に強力です。

  • 得意:外側の件数が少ない/右表の結合キーに有効なインデックスがある/選択度が高い条件で絞れる
  • 苦手:外側が大量行で内側探索を何度も繰り返す/内側がインデックス未整備で都度スキャンになる

sql left join でNested Loopsが選ばれて遅い場合は、「右テーブルの結合キーにインデックスが効いているか」「外側の行数を先に減らせないか」を重点的に疑います。

マージ結合(Merge Join)の特徴

Merge Joinは、結合キーで両入力がソート(またはソート済みインデックス順)されている前提で、先頭から順に突き合わせていく方式です。大量データ同士でも、並びが整っていれば安定して高速になりやすい一方、ソートが必要になると一気にコストが増えます。

  • 得意:両テーブルが結合キー順に読み出せる(適切なインデックスがある等)/大規模データ
  • 苦手:事前ソートが発生しメモリ・CPUを消費/ソートがディスクに落ちると顕著に遅くなる

実行計画にSortが見える場合、Merge Join自体よりも「ソートさせられていること」がボトルネックになっていることがあります。

ハッシュ結合(Hash Join)の特徴

Hash Joinは、一方の入力(ビルド側)からハッシュテーブルを作り、もう一方(プローブ側)を走査してハッシュで突き合わせる方式です。等価結合(=)で大量データを結合するケースで選ばれやすい反面、メモリ使用量の影響を強く受けます。

  • 得意:等価結合/大規模データ同士/インデックスが弱くても成立しやすい
  • 苦手:メモリ不足でディスク退避が発生(スピル)/ビルド側が想定以上に大きい

sql left join でHash Joinが遅い場合は、ビルド側に選ばれているテーブルが巨大になっていないか、推定と実績がズレていないかを確認するのが重要です。

インメモリハッシュ結合の概要

インメモリハッシュ結合は、ハッシュテーブルが必要メモリ内に収まり、ディスク退避なしで完結する理想的なHash Joinの状態を指します。メモリ内で完了すれば、結合のスループットが高く安定しやすいです。

  • 観点:ビルド側の行数・行幅が増えると必要メモリが増大します
  • 確認:実行計画/実行統計で「メモリ付与」「スピル有無」を追えるDBでは必ずチェックします

猶予ハッシュ結合の概要

猶予ハッシュ結合は、メモリが不足しそうな場合に、処理を段階化したり一部を退避したりして実行を継続するタイプの挙動を指す文脈で使われます。結果として「途中から重くなる」「環境や同時実行数で速度がブレる」原因になりやすいのが特徴です。

  • 兆候:実行時間のばらつき、負荷状況で急に遅くなる
  • 方向性:ビルド側を小さくする(事前絞り込み)/行幅を減らす(不要列をSELECTしない)などでメモリ要求を下げる

再帰的ハッシュ結合の概要

再帰的ハッシュ結合は、メモリに収まりきらない大きな入力を複数のパーティションに分割し、段階的(再帰的)にハッシュ結合することで成立させる考え方です。成立はするものの、I/Oや分割コストが増えやすく、インメモリで完結する場合より遅くなりがちです。

  • リスク:データ量増加に伴って急に閾値を超え、処理形態が重くなる
  • 方向性:結合前に件数を落とす/統計情報を適正化して過小見積もりを減らす

ハッシュ結合のベイルアウト(回避策の方向性)

ハッシュ結合のベイルアウトは、ハッシュ結合が想定通りに効かない(メモリ不足、行数見積り違い等)状況で、別の手段に「逃がす」必要がある場面の総称として捉えると理解しやすいです。対処はDBや状況で異なりますが、方向性は次の通りです。

  • ビルド側を小さくする:結合前に絞り込み、不要な列を減らして行幅を小さくする
  • 推定精度を上げる:統計情報の適正化により、誤ったJOIN方式選択を減らす
  • インデックスで別方式を成立させる:右表の結合キーにインデックスを用意し、Nested Loopsが有利になる状況を作る

重要なのは「Hash Joinが悪い」のではなく、「そのHash Joinがインメモリで成立していない/見積りが外れている」ことが問題である点です。

適応型JOINの概要(状況に応じた最適化)

適応型JOINは、実行時の実データ状況を見ながら、途中でJOIN方式を切り替えたり、より有利な経路を選び直したりする最適化の仕組みです。sql left join のように、推定行数と実績行数がズレやすいクエリでは、うまく働けば性能の安定化に寄与します。

一方で、環境や互換性設定、クエリ形状によっては期待通りにならず、計画の再利用性や挙動の予測が難しくなることもあります。実行計画上で「適応(Adaptive)」に関する表示や注記が出るDBでは、意図した切り替えが起きているかを確認するのが有効です。

適応型JOINの対象となるステートメント

適応型JOINの対象は、一般に「複数のJOIN方式が候補になりうる」「推定誤差があると選択ミスが致命的になりうる」ステートメントです。特に、LEFT JOINを含み、片側の絞り込み条件次第で結果件数が大きく振れるようなクエリでは対象になりやすい傾向があります。

適応型JOINのしきい値(切り替わる条件)

適応型JOINには、切り替え判断を行うための「しきい値」が存在します。これは、実行時に観測された行数やコストがある基準を超える(または下回る)ことで、当初想定していたJOIN方式から別方式へ切り替える、といった条件を意味します。

実務上は、しきい値そのものを暗記するよりも、「なぜ切り替えが必要になったか(推定と実績の差、メモリ事情、絞り込みの効き方)」を実行計画と実行時統計から読み取ることが重要です。

互換性レベル変更なしで適応型JOINを無効化する方法

適応型JOINが原因で挙動が不安定になる場合、互換性レベルを変更せずに無効化したいケースがあります。この場合は、DBが提供するクエリヒントやセッション/ステートメント単位のオプションで制御するのが一般的な方向性です。

ただし、無効化は「最適化の恩恵も同時に捨てる」ことになるため、適用は限定的にし、まずは実行計画で適応の切り替えが本当にボトルネックになっているかを確認した上で判断します。

DB別のLEFT JOIN注意点(MySQL/SQL Serverなど)

sql+leftjoin+database

同じsql left joinでも、DB製品ごとの「文字列比較のルール」「NULLの扱い」「オプティマイザの最適化方針」などが違うため、結果の差分や性能劣化が起こり得ます。ここではMySQLとSQL Serverを中心に、LEFT JOINを安全に運用・移植するための注意点を整理します。

MySQLでのLEFT JOINの落とし穴(文字コード/照合順序/NULL)

MySQLのLEFT JOINで意外に多いのが、結合キーが「文字列」のときに起こる不一致や、照合順序(collation)由来の比較挙動の違いです。クエリ自体は正しく見えても、結合されずに右側がNULLだらけになったり、逆に想定外の一致が起こったりします。

  • 文字コード・照合順序の不一致で結合できない/意図せず結合される

    テーブル間でカラムの文字コード(例:utf8mb4)や照合順序(例:utf8mb4_0900_ai_ci)が異なると、比較時に暗黙変換が入ったり、比較ルールが変わったりします。特に「大文字小文字を区別しない(_ci)」照合順序では、'A''a'が同一扱いになり、LEFT JOINの一致条件が広がることがあります。

    対策としては、結合キーの定義を揃える(同じ文字コード・照合順序に統一)ことが最優先です。暫定対応でクエリ側にCOLLATEを付けて比較ルールを指定する方法もありますが、条件式が複雑になり、インデックスが効きにくくなる場合があります。

    SELECT *
    FROM t1
    LEFT JOIN t2
      ON t1.code COLLATE utf8mb4_0900_as_cs = t2.code;

    注意:結合条件に関数やCOLLATE指定が入ると、インデックス利用が制限されて性能問題につながることがあります。

  • 空文字(”)とNULLの混同に注意

    MySQLでは空文字とNULLは別物ですが、データ投入・移行過程で「未設定」を空文字で持っているテーブルとNULLで持っているテーブルが混在すると、LEFT JOINの一致条件が崩れます。例えば、片方がNULL、もう片方が''だと、=比較では一致しません。

    正規化としては「未設定はNULLに統一」または「未設定は空文字に統一」が安全です。やむを得ずクエリで吸収する場合は、比較前に揃える処理が必要になります(ただし前述同様、インデックスに影響し得ます)。

    SELECT *
    FROM t1
    LEFT JOIN t2
      ON COALESCE(t1.code, '') = COALESCE(t2.code, '');
  • NULLを含む結合キーは「一致しない」前提で設計する

    MySQLに限りませんが、結合条件がt1.key = t2.keyのような等価比較の場合、NULLは何とも一致しません(NULL = NULLも真にはならない)。その結果、LEFT JOINで右側がNULLになり、「データがないように見える」状態になります。

    設計としては結合キーをNULLにしない(NOT NULL制約、または代替キー利用)が理想です。どうしてもNULLを同一視して結合したい場合は、MySQL特有のNULLセーフ等価演算子<=>を検討できます。

    SELECT *
    FROM t1
    LEFT JOIN t2
      ON t1.key <=> t2.key;  -- NULL同士も一致扱い

    注意:<=>はMySQL方言のため、他DBへの移植性は下がります。

SQL ServerでのLEFT JOIN最適化の観点(統計情報・互換性)

SQL Serverでは、LEFT JOINの結果そのものよりも「最適化(どの順序・方式でJOINするか)」が性能を大きく左右します。特に統計情報の鮮度や互換性レベルの設定が、実行計画に影響して想定外に遅くなるケースがあります。

  • 統計情報の鮮度が実行計画を左右する

    SQL Serverのオプティマイザは統計情報を使って行数見積もりを行い、LEFT JOINの結合順序やJOINアルゴリズムを決めます。統計情報が古いと見積もりが外れ、必要以上に重いプランになりやすいです。

    大量更新・一括投入後に急に遅くなった場合は、対象テーブル/インデックスの統計情報更新が改善につながることがあります。

    -- 統計情報の更新例(対象テーブル)
    UPDATE STATISTICS dbo.TableA;
  • 互換性レベルやCE(Cardinality Estimator)の違いでプランが変わる

    SQL Serverはバージョンやデータベース互換性レベルにより、行数推定のロジック(CE)が異なる場合があります。同じsql left joinでも、互換性レベルを上げた途端にプランが変わり、速くなる/遅くなるが起こり得ます。

    移行直後や互換性レベル変更後に性能差が出た場合は、実行計画の差分を確認し、必要ならクエリの書き方やインデックス、統計の見直しで吸収する方針が現実的です。

  • 暗黙変換(implicit conversion)があるとJOINが急に重くなる

    結合キーのデータ型が左右で一致していないと、SQL Serverは暗黙変換を行います。このとき、インデックスを活かせない比較になり、LEFT JOINがスキャン寄りになって遅くなることがあります。

    代表例は、VARCHARNVARCHARの混在、数値と文字列の混在です。対策は「スキーマ側で型を揃える」が最優先で、クエリ側のCASTは最後の手段です。

    -- 例:型不一致を疑うケース(片側がNVARCHAR等)
    SELECT *
    FROM A
    LEFT JOIN B
      ON A.Code = B.Code;  -- 実際には暗黙変換が入っている可能性

他DBへの移植時に気をつけるポイント(方言・FULL OUTER代替)

LEFT JOIN自体は標準SQLに近い構文ですが、実務では「NULLの同一視」「外部結合の種類」「関数・演算子」といった周辺要素に方言が混ざりやすく、移植時に差分が出ます。特に複数DBで共通運用する場合は、方言の混入を最小化するのがコツです。

  • MySQL特有の演算子・関数を結合条件に使うと移植性が下がる

    前述の<=>(NULLセーフ等価)や、特定の文字列関数をJOIN条件に入れると、他DBでそのまま動きません。移植を見据えるなら「結合キーはNOT NULLに寄せる」「前処理で正規化してからJOINする」など、SQL方言に頼らない設計が安全です。

  • FULL OUTER JOIN非対応DBでは代替が必要

    DBによってはFULL OUTER JOINが使えない/制限がある場合があります。その場合、左右のLEFT JOIN結果を結合して代替する実装が選択肢になります。ただし、重複行の扱い、NULLの出方、性能特性が変わりやすいため、移植時は検証が必須です。

    -- FULL OUTER JOINの代替例(概念)
    SELECT ...
    FROM A
    LEFT JOIN B ON A.id = B.id
    UNION
    SELECT ...
    FROM B
    LEFT JOIN A ON A.id = B.id;

    注意:UNION/UNION ALLの選択で重複排除の有無が変わり、結果も性能も大きく変化します。

  • 照合順序・大小区別・NULLソートなど「比較ルール」の差分を洗い出す

    LEFT JOINの一致判定は「比較ルール」に依存します。移植時には、文字列比較が大小区別かどうか、アクセント等の扱い、NULLの比較や並び順の扱いが同じかを確認し、必要ならスキーマ側(文字コード・照合順序)で統一します。クエリでの場当たり的な吸収は、将来的な保守性と性能の両面で負債になりやすい点に注意してください。

まとめ:LEFT JOINを安全に使いこなすチェックリスト

sql+leftjoin+optimization

sql left joinは「左テーブルの行を残しつつ、右テーブルが存在しない場合は列がNULLになる」という性質が強力な一方で、結果の行数が想定外になったり、条件の置き方次第で実質INNER JOINのような結果になったり、性能が急に悪化したりしやすい構文でもあります。最後に、実務で事故を減らすための確認観点をチェックリストとして整理します。

期待する行数・NULL・フィルタ位置を事前に確認する

LEFT JOINで起きがちなトラブルの多くは、「何行残るべきか」「どの列がNULLになり得るか」「どこで絞り込むか」を曖昧なまま書き始めることが原因です。クエリを書いたら、まず“仕様として期待する結果”を明文化してからSQLを当てはめると安全です。

  • 左テーブルの行数が基本の上限になる
    sql left joinでは、右側に一致がなくても左側の行は残ります。まず「左テーブル単体の行数」と「最終的に欲しい行数」が一致する想定かを確認します。一致しない場合は、どの条件で減る(WHEREで絞る)のか、どの条件で増える(右側の複数一致)可能性があるのかを先に整理します。

  • 右テーブルが複数一致すると行が増える前提で考える
    右側が1行とは限りません。右テーブルの結合キーがユニークでないと、左1行に対して右が複数行マッチし、結果が増えます。「結果を増やして良いJOINなのか(明細を展開したいのか)」「本当は代表1行だけが欲しいのか」を事前に判断し、増えて困るなら右側の粒度(ユニーク性)を再確認します。

  • NULLになる列を“仕様として許容するか”を決める
    LEFT JOINの目的は“存在しないものも含める”ことなので、右テーブル由来の列はNULLになり得ます。NULLが出てよい列・出てはいけない列を分け、後工程(表示・集計・アプリ処理)での取り扱いを想定しておきます。

  • フィルタ条件は「ONに置くか、WHEREに置くか」を必ず意識する
    LEFT JOINでは、右テーブルに関する条件をWHEREに置くと、右がNULLの行が落ちやすくなり、結果としてLEFT JOINの意味が薄れる(意図せず欠損行が消える)ことがあります。欠損行も残したい条件はON側で扱う、欠損行を落としてよい条件はWHERE側で絞る、という方針をクエリ作成前に決めておくと事故を防げます。

  • 検算用の“簡易チェック”を用意する
    作成したsql left joinが期待どおりかは、以下のような観点で素早く検算できます。

    • 「左テーブルの件数」と「JOIN後の件数」を比較し、増減の理由が説明できるか

    • 右テーブルが存在しないはずのケースで、右側の列がNULLになっているか

    • フィルタ条件を一時的に外した場合と比較して、欠損行が消えていないか

実行計画とインデックスで性能劣化を防ぐ

sql left joinは、データ量が増えるほど結合コストが支配的になりやすく、開発環境では問題なくても本番データで急に遅くなることがあります。安全に使うには「結果の正しさ」だけでなく、「実行計画を見て妥当なアクセスになっているか」をセットで確認するのが重要です。

  • 実行計画で最初に見るべきは“駆動表”と“結合の順序”
    LEFT JOINは左側の行を残すため、一般に左テーブルが基準になります。実行計画上も、どのテーブルから読み始め、どの順に結合しているかを確認し、想定外に大きい表から全件スキャンしていないかを見ます。

  • 結合キーにインデックスが効いているかを確認する
    性能劣化の典型は、結合条件に使っている列に適切なインデックスがなく、右テーブル側の探索が繰り返されるケースです。特に「左の行数が多い×右の検索が遅い」の組み合わせは悪化しやすいため、結合キー(および結合と同時に使う絞り込み列)にインデックスがあるか、実行計画でインデックスアクセスになっているかを確認します。

  • 不要な列を減らし、先に絞れるものは先に絞る
    SELECT *のように列を取りすぎるとI/Oが増え、結合後の処理も重くなります。必要列だけに絞り、左側で確実に減らせる条件(欠損行を落とさない範囲で)を早めに適用できているかを見直します。結果の意味を変えない範囲でデータ量を減らすことが、LEFT JOINの性能改善の近道です。

  • “遅い”は体感ではなく、計測と比較で判断する
    同じsql left joinでも、条件やデータ分布の違いで実行計画が変わり、急に遅くなることがあります。実行計画の確認に加え、変更前後で処理時間・読み取り行数などを比較し、「どこがボトルネックか」を特定してから対策します。

  • 性能対策は「正しさを崩さない」前提で行う
    LEFT JOINは結果の意味が繊細です。高速化のために条件位置や結合順を変えた結果、欠損行が消えたり行数が増えたりすると本末転倒です。チューニング後は必ず、前項の“期待する行数・NULL・フィルタ位置”の観点で再検証し、性能と正しさを両立させます。