メシのタネ

メシのタネになる、Laravelや設計思想の技術配信サイト


MySQL LEFT JOIN 完全ガイド(2025年版)


  1. DB
  2. MySQL LEFT JOIN 完全ガイド(2025年版)

はじめに――mysql left join は「結合対象の右テーブルに一致する行がなくても左テーブルの行を残す」機能です。この記事では構文から実戦的な落とし穴、性能チューニングまで 初学者~中級者が知りたいポイント を 1 ページでまとめます。

この記事で得られること

  • LEFT JOIN の正しい書き方と動く実例
  • NULL 行が消える・意図しないフィルタリングなど典型的トラブルの回避策
  • EXPLAIN とインデックスで 10× 速くする 5 箇条
  • Laravel クエリビルダとの橋渡し情報

LEFT JOIN とは何か

一文で言うと:「左テーブルを主とし、右テーブルにヒットしなければ NULL を補完して返す結合」。

  • 用途: マスタ付き一覧、ログ+ユーザ情報、月次集計の “欠損行” 保持など
  • INNER JOIN との違い: INNER は両テーブルに一致する行のみ返すため “欠損” が落ちる点が決定的

LEFT / RIGHT / FULL の位置関係

種類優先テーブル不一致行の扱い
LEFT JOIN左 (FROM 句側)NULL 行を残す
RIGHT JOINNULL 行を残す
FULL JOIN*両方両方に残す

注:MySQL では FULL JOIN を直接サポートしていないため UNION で代用します。

基本構文と実例

最小構文は下記のとおりです(ON 句が必須)。

SELECT
  u.id, u.name, p.phone
FROM users AS u
LEFT JOIN phones AS p
  ON p.user_id = u.id;

実例 1:欠損を残すマスタ付き一覧

SELECT
  m.month,
  COALESCE(s.amount, 0) AS amount
FROM months AS m
LEFT JOIN sales AS s
  ON s.month = m.month
ORDER BY m.month;
  • COALESCE()NULL を 0 へ変換し「集計抜け」を防ぎます。
  • Laravel なら leftJoinselectRaw('COALESCE(s.amount,0) as amount') で同等。

よくある落とし穴と対処法

WHERE 句に右テーブルの列を書いてしまう

  • WHERE p.phone IS NOT NULL は LEFT JOIN を実質 INNER JOIN に変えてしまいます。
  • ON 句または HAVING 句に条件を書く。

GROUP BY で NULL 行が消える

  • 集約前に COALESCE() で置き換えるか、ダミー行を UNION で足す。

複合キー JOIN の一部が NULL

  • ON (a.x = b.x OR (a.x IS NULL AND b.x IS NULL)) のような冗長判定より、
    結合キーを単一 NOT NULL 列に正規化する方が高速。

多対多の LEFT JOIN で倍増行

  • DISTINCT やサブクエリで重複を削る。理想は EXISTS で書き換え。

NULL と空文字の混在

  • 文字列型の JOIN キーなら TRIM()NULLIF() で事前正規化。

EXPLAIN で実行計画を読む

クエリチューニングの第一歩は EXPLAIN結合順序使用インデックス を確認しましょう。

EXPLAIN FORMAT=TRADITIONAL
SELECT u.id, p.phone
FROM users AS u
LEFT JOIN phones AS p
  ON p.user_id = u.id
  • type 列が ALL なら全表走査。refeq_ref を目指す。
  • possible_keys にインデックス候補が出ていなければ、結合キーに (user_id) を追加。
  • 8.0.24+ は EXPLAIN ANALYZE で実行時間も取得可。

パフォーマンス最適化 5 箇条

  1. 結合キーは必ずインデックス(FK 列 + 参照側 PK の複合などは要注意)
  2. JOIN キーに関数をかけないDATE(created_at) は非索引化)
  3. 取得列を絞りカバリングインデックスSELECT * を捨てる)
  4. 大規模集計は WITH (CTE) + 一時テーブルで分割
  5. EXPLAIN → インデックスヒント で強制指定も最終手段として活用

LEFT JOININNER JOIN のどっちを選べばいい?

  • 欠損行を残して集計したい → LEFT JOIN
  • 両テーブルに揃った行だけで OK → INNER JOIN

WHERE に右テーブルの列を書いたら行が減ったんだけど?

LEFT JOIN の意味が消えます。WHERE phones.phone IS NOT NULL のような条件は、ON 句か HAVING 句へ移動しましょう。

GROUP BY 後に NULL 行が消えるのを防ぐ方法は?

集計の前に COALESCE() で NULL → 0 などに変換するか、先にサブクエリで集計してから LEFT JOIN します。

結合キーに関数 (DATE(created_at) など) を書いてもいい?

速度が激落ちします。インデックスが効かなくなるため

  1. 生の列で結合
  2. 必要なら表示側で DATE_FORMAT() などを当てる
    が鉄則です。

多対多の LEFT JOIN で行が倍増して困る!

中間テーブルをサブクエリで一意にまとめてから JOIN するか、DISTINCTEXISTS を検討してください。根本はテーブル設計の見直しが近道です。

LEFT JOIN が遅いとき何から疑えばいい?

  1. 結合キーに 単体インデックス があるか
  2. 取得列を絞って カバリングインデックス を作れるか
  3. EXPLAINtype = ALL(全表走査)になっていないか
    この 3 点を順にチェックするだけで大半のボトルネックは解消します。

FULL JOIN がないけど両方の欠損行を取りたい!

LEFT と RIGHT を UNION ALL で重ね、重複行を DISTINCT で消すのが定番です。実装コストが高いならアプリ層でマージする手もあり。


コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

This site uses Akismet to reduce spam. Learn how your comment data is processed.