データベースを使っていると、「顧客テーブルと注文テーブルを組み合わせて、誰がいくら注文したか知りたい」という場面に出会います。このように、複数のテーブルのデータを結合して取得する操作がJOIN(結合)です。
中でもLEFT JOIN(左外部結合)は、実務で最もよく使われる結合方法の一つです。この記事では、LEFT JOINの基本から実践的な使い方まで、分かりやすく解説していきます。
LEFT JOINとは?基本を理解しよう

JOINの基本概念
JOINとは、複数のテーブルを結合して、関連するデータを一度に取得する操作です。
例えば、こんな2つのテーブルがあるとします:
顧客テーブル(customers):
| customer_id | name |
|---|---|
| 1 | 田中太郎 |
| 2 | 佐藤花子 |
| 3 | 鈴木一郎 |
注文テーブル(orders):
| order_id | customer_id | amount |
|---|---|---|
| 101 | 1 | 5000 |
| 102 | 1 | 3000 |
| 103 | 2 | 2000 |
JOINを使えば、「田中太郎さんが5000円の注文をした」という情報を1つのクエリで取得できます。
LEFT JOINの定義
LEFT JOIN(左外部結合)は、左側のテーブルのすべての行を取得し、右側のテーブルに一致するデータがあれば結合する方法です。
重要なのは、右側に一致するデータがなくても、左側の行は必ず結果に含まれるという点です。
LEFT JOINの動作を図で理解する
具体例で見てみましょう。
テーブルの準備
顧客テーブル:
| customer_id | name |
|---|---|
| 1 | 田中太郎 |
| 2 | 佐藤花子 |
| 3 | 鈴木一郎 |
注文テーブル:
| order_id | customer_id | amount |
|---|---|---|
| 101 | 1 | 5000 |
| 102 | 2 | 3000 |
注目:鈴木一郎さん(customer_id = 3)には注文がありません。
LEFT JOINの実行
SELECT
customers.name,
orders.order_id,
orders.amount
FROM customers
LEFT JOIN orders ON customers.customer_id = orders.customer_id;
結果
| name | order_id | amount |
|---|---|---|
| 田中太郎 | 101 | 5000 |
| 佐藤花子 | 102 | 3000 |
| 鈴木一郎 | NULL | NULL |
ポイント:
鈴木一郎さんには注文がありませんが、LEFT JOINを使っているので結果に含まれます。注文情報の部分はNULLになります。
INNER JOINとLEFT JOINの違い
最も混同されやすいINNER JOINとの違いを明確にしましょう。
INNER JOIN(内部結合)
両方のテーブルに一致するデータがある行だけを取得します。
SELECT
customers.name,
orders.order_id,
orders.amount
FROM customers
INNER JOIN orders ON customers.customer_id = orders.customer_id;
結果:
| name | order_id | amount |
|---|---|---|
| 田中太郎 | 101 | 5000 |
| 佐藤花子 | 102 | 3000 |
鈴木一郎さんは結果に含まれません。
LEFT JOIN(左外部結合)
左側のテーブルのすべての行を取得します。
SELECT
customers.name,
orders.order_id,
orders.amount
FROM customers
LEFT JOIN orders ON customers.customer_id = orders.customer_id;
結果:
| name | order_id | amount |
|---|---|---|
| 田中太郎 | 101 | 5000 |
| 佐藤花子 | 102 | 3000 |
| 鈴木一郎 | NULL | NULL |
鈴木一郎さんも結果に含まれます。
どちらを使うべき?
INNER JOINを使う場面:
- 両方のテーブルに存在するデータだけが必要
- 「注文した顧客の一覧」が欲しい
LEFT JOINを使う場面:
- 左側のテーブルのすべての行が必要
- 「注文していない顧客も含めた全顧客の一覧」が欲しい
LEFT JOINの基本的な書き方
SQLの構文を詳しく見ていきましょう。
基本構文
SELECT 列名1, 列名2, ...
FROM 左テーブル
LEFT JOIN 右テーブル ON 結合条件;
実際の例
SELECT
customers.customer_id,
customers.name,
orders.order_id,
orders.amount
FROM customers
LEFT JOIN orders ON customers.customer_id = orders.customer_id;
構文の説明:
FROM customers:左側のテーブル(基準となる)LEFT JOIN orders:右側のテーブル(結合する)ON customers.customer_id = orders.customer_id:結合条件
テーブルに別名を付ける
長いテーブル名を毎回書くのは面倒なので、別名(エイリアス)を使えます:
SELECT
c.customer_id,
c.name,
o.order_id,
o.amount
FROM customers AS c
LEFT JOIN orders AS o ON c.customer_id = o.customer_id;
ASは省略できます:
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
実践的な使用例

実際の業務でよく使うパターンを紹介します。
例1:注文していない顧客を見つける
SELECT
c.customer_id,
c.name
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
WHERE o.order_id IS NULL;
ポイント:WHERE o.order_id IS NULLで、注文テーブルに存在しない顧客だけを抽出できます。
結果:
| customer_id | name |
|---|---|
| 3 | 鈴木一郎 |
例2:顧客ごとの注文件数を集計
SELECT
c.customer_id,
c.name,
COUNT(o.order_id) AS order_count
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.name;
結果:
| customer_id | name | order_count |
|---|---|---|
| 1 | 田中太郎 | 2 |
| 2 | 佐藤花子 | 1 |
| 3 | 鈴木一郎 | 0 |
注文がない顧客も、カウント0として表示されます。
例3:最新の注文日を取得
SELECT
c.customer_id,
c.name,
MAX(o.order_date) AS last_order_date
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.name;
注文履歴がない顧客のlast_order_dateはNULLになります。
例4:合計注文金額を計算
SELECT
c.customer_id,
c.name,
COALESCE(SUM(o.amount), 0) AS total_amount
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.name;
ポイント:COALESCE()関数を使うと、NULLを0に変換できます。
複数のテーブルをLEFT JOINする
3つ以上のテーブルを結合することもできます。
3つのテーブルの例
テーブル構成:
- customers(顧客)
- orders(注文)
- products(商品)
SELECT
c.name AS customer_name,
o.order_id,
p.product_name,
o.quantity
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
LEFT JOIN products p ON o.product_id = p.product_id;
結合の順序:
- customersとordersを結合
- その結果とproductsを結合
複数結合の注意点
LEFT JOINを複数使うとき、結合の順序が重要です:
-- customersを基準に、すべての顧客を取得
FROM customers c
LEFT JOIN orders o ON ...
LEFT JOIN order_details od ON ...
2番目以降のLEFT JOINも、左側のテーブル(この場合はorders)に存在しない場合はNULLになります。
NULL値の扱い方
LEFT JOINでは、NULLが多く出現します。適切に処理しましょう。
NULL値の判定
-- NULLかどうかを判定
WHERE o.order_id IS NULL
-- NULLではないことを判定
WHERE o.order_id IS NOT NULL
注意:= NULLや!= NULLは正しく動作しません。必ずIS NULLやIS NOT NULLを使ってください。
NULL値を別の値に置き換える
-- COALESCE関数(複数のデータベースで使える)
SELECT
c.name,
COALESCE(o.amount, 0) AS amount
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id;
-- IFNULL関数(MySQLの場合)
SELECT
c.name,
IFNULL(o.amount, 0) AS amount
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id;
-- ISNULL関数(SQL Serverの場合)
SELECT
c.name,
ISNULL(o.amount, 0) AS amount
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id;
NULL値での計算に注意
-- 間違い:NULLは計算に含まれない
SELECT SUM(amount) FROM orders; -- NULLは無視される
-- 正しい:先にNULLを0に変換
SELECT SUM(COALESCE(amount, 0)) FROM orders;
LEFT JOINのパフォーマンス
大量のデータを扱う場合、パフォーマンスが重要です。
インデックスの重要性
結合条件に使う列には、インデックスを作成しましょう:
-- 外部キーにインデックスを作成
CREATE INDEX idx_orders_customer_id ON orders(customer_id);
インデックスがないと、結合処理が非常に遅くなります。
必要な列だけを取得
-- 悪い例:すべての列を取得
SELECT *
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id;
-- 良い例:必要な列だけを取得
SELECT
c.name,
o.order_id,
o.amount
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id;
WHERE句の位置に注意
-- パターン1:LEFT JOINの後でフィルタリング
SELECT c.name
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
WHERE c.city = '東京';
-- パターン2:結合前にフィルタリング(サブクエリ)
SELECT c.name
FROM (SELECT * FROM customers WHERE city = '東京') c
LEFT JOIN orders o ON c.customer_id = o.customer_id;
どちらが速いかは、データ量やインデックスの状況によります。
よくある間違いと注意点
初心者がつまずきやすいポイントを紹介します。
間違い1:WHERE句でNULLをフィルタリングしてしまう
-- 間違い:これだとINNER JOINと同じ結果になる
SELECT c.name, o.amount
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
WHERE o.amount > 1000;
WHERE o.amount > 1000により、amountがNULLの行(注文がない顧客)が除外されてしまいます。
解決策:
-- 正しい:ONの中に条件を入れる
SELECT c.name, o.amount
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
AND o.amount > 1000;
間違い2:結合条件を間違える
-- 間違い:等号の位置が逆
LEFT JOIN orders o ON o.customer_id = c.customer_id -- これでもOKだが
-- 正しい:左テーブルの列 = 右テーブルの列
LEFT JOIN orders o ON c.customer_id = o.customer_id
等号の左右はどちらでも動作しますが、慣習的に「左テーブルの列 = 右テーブルの列」と書きます。
間違い3:テーブルの順序を間違える
-- 意図と違う結果になる
SELECT c.name, o.amount
FROM orders o
LEFT JOIN customers c ON o.customer_id = c.customer_id;
これだと「すべての注文」が基準になり、顧客のいない注文(通常はあり得ない)も表示されます。
間違い4:ASを忘れて別名が曖昧になる
-- 曖昧:どちらのcustomer_idか不明
SELECT customer_id
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id;
-- 明確:テーブル名(または別名)を付ける
SELECT c.customer_id
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id;
LEFT JOINの実践例:ECサイト

実際のシステムで使うようなクエリを見てみましょう。
例1:顧客の購入履歴レポート
SELECT
c.customer_id,
c.name,
c.email,
COUNT(o.order_id) AS total_orders,
COALESCE(SUM(o.amount), 0) AS total_spent,
MAX(o.order_date) AS last_order_date
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.name, c.email
ORDER BY total_spent DESC;
これで、購入履歴がない顧客も含めた、完全な顧客リストが得られます。
例2:休眠顧客の抽出
SELECT
c.customer_id,
c.name,
c.email,
MAX(o.order_date) AS last_order_date
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.name, c.email
HAVING MAX(o.order_date) < DATE_SUB(NOW(), INTERVAL 6 MONTH)
OR MAX(o.order_date) IS NULL;
6ヶ月以上注文がない、または一度も注文していない顧客を抽出します。
例3:商品の販売実績
SELECT
p.product_id,
p.product_name,
COUNT(od.order_id) AS times_ordered,
COALESCE(SUM(od.quantity), 0) AS total_quantity_sold
FROM products p
LEFT JOIN order_details od ON p.product_id = od.product_id
GROUP BY p.product_id, p.product_name
ORDER BY total_quantity_sold DESC;
一度も売れていない商品も含めて、全商品の販売実績を確認できます。
他のJOINとの比較
LEFT JOIN以外の結合方法も簡単に紹介します。
RIGHT JOIN(右外部結合)
LEFT JOINの逆です。右側のテーブルのすべての行を取得します。
SELECT c.name, o.amount
FROM customers c
RIGHT JOIN orders o ON c.customer_id = o.customer_id;
実務では、LEFT JOINを使うのが一般的で、RIGHT JOINはあまり使われません。テーブルの順序を入れ替えれば、LEFT JOINで同じことができるためです。
FULL OUTER JOIN(完全外部結合)
両方のテーブルのすべての行を取得します。
SELECT c.name, o.amount
FROM customers c
FULL OUTER JOIN orders o ON c.customer_id = o.customer_id;
注意:
MySQLはFULL OUTER JOINをサポートしていません。UNIONを使って同じ結果を得られます。
CROSS JOIN(クロス結合)
すべての組み合わせを生成します(直積)。
SELECT c.name, p.product_name
FROM customers c
CROSS JOIN products p;
顧客が3人、商品が5個あれば、結果は15行になります。
データベース別の注意点
主要なデータベースでのLEFT JOINの違いを紹介します。
MySQL / MariaDB
-- 標準的な書き方
SELECT c.name, o.amount
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id;
-- 古い書き方(非推奨)
SELECT c.name, o.amount
FROM customers c
LEFT OUTER JOIN orders o ON c.customer_id = o.customer_id;
OUTERは省略可能です。
PostgreSQL
-- 標準的な書き方
SELECT c.name, o.amount
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id;
PostgreSQLは標準SQLに最も準拠しています。
SQL Server
-- 標準的な書き方
SELECT c.name, o.amount
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id;
-- 古い書き方(非推奨、SQL Server 2012以降は使用不可)
SELECT c.name, o.amount
FROM customers c, orders o
WHERE c.customer_id *= o.customer_id;
Oracle
-- 標準的な書き方(Oracle 9i以降)
SELECT c.name, o.amount
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id;
-- 古い書き方(非推奨だが、まだ見かける)
SELECT c.name, o.amount
FROM customers c, orders o
WHERE c.customer_id = o.customer_id(+);
まとめ:LEFT JOINを使いこなそう
LEFT JOINは、SQLで最も重要な機能の一つです。マスターすれば、複雑なデータ分析が可能になります。
この記事の重要ポイント:
- LEFT JOINは左側のテーブルのすべての行を取得
- 右側に一致するデータがない場合はNULL
- INNER JOINは両方に存在する行だけ取得
- NULL値の扱いに注意が必要
- インデックスを活用してパフォーマンスを向上
- WHERE句とON句の違いを理解する
基本構文:
SELECT 列名
FROM 左テーブル
LEFT JOIN 右テーブル ON 結合条件;
よく使うパターン:
-- パターン1:一致しないデータを見つける
WHERE 右テーブルの列 IS NULL
-- パターン2:集計(カウント、合計)
COUNT(右テーブルの列)
SUM(COALESCE(右テーブルの列, 0))
-- パターン3:複数テーブルの結合
FROM テーブルA
LEFT JOIN テーブルB ON 条件1
LEFT JOIN テーブルC ON 条件2
実践のヒント:
- まず必要なデータを整理する
- どのテーブルを基準(左側)にするか決める
- 結合条件を正しく設定する
- NULL値の処理を忘れずに
- 必要な列だけをSELECTする
LEFT JOINを使えば、「全顧客の購入履歴(購入していない人も含む)」「全商品の販売実績(売れていない商品も含む)」といった、包括的なレポートが作成できます。
実際のプロジェクトで何度も使って、体で覚えていきましょう。最初は難しく感じても、すぐに慣れるはずです!

コメント