LEFT JOINとは?SQLのテーブル結合を完全マスター

データベース・SQL

データベースを使っていると、「顧客テーブルと注文テーブルを組み合わせて、誰がいくら注文したか知りたい」という場面に出会います。このように、複数のテーブルのデータを結合して取得する操作がJOIN(結合)です。

中でもLEFT JOIN(左外部結合)は、実務で最もよく使われる結合方法の一つです。この記事では、LEFT JOINの基本から実践的な使い方まで、分かりやすく解説していきます。

スポンサーリンク

LEFT JOINとは?基本を理解しよう

JOINの基本概念

JOINとは、複数のテーブルを結合して、関連するデータを一度に取得する操作です。

例えば、こんな2つのテーブルがあるとします:

顧客テーブル(customers):

customer_idname
1田中太郎
2佐藤花子
3鈴木一郎

注文テーブル(orders):

order_idcustomer_idamount
10115000
10213000
10322000

JOINを使えば、「田中太郎さんが5000円の注文をした」という情報を1つのクエリで取得できます。

LEFT JOINの定義

LEFT JOIN(左外部結合)は、左側のテーブルのすべての行を取得し、右側のテーブルに一致するデータがあれば結合する方法です。

重要なのは、右側に一致するデータがなくても、左側の行は必ず結果に含まれるという点です。

LEFT JOINの動作を図で理解する

具体例で見てみましょう。

テーブルの準備

顧客テーブル:

customer_idname
1田中太郎
2佐藤花子
3鈴木一郎

注文テーブル:

order_idcustomer_idamount
10115000
10223000

注目:鈴木一郎さん(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;

結果

nameorder_idamount
田中太郎1015000
佐藤花子1023000
鈴木一郎NULLNULL

ポイント:
鈴木一郎さんには注文がありませんが、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;

結果:

nameorder_idamount
田中太郎1015000
佐藤花子1023000

鈴木一郎さんは結果に含まれません

LEFT JOIN(左外部結合)

左側のテーブルのすべての行を取得します。

SELECT 
    customers.name,
    orders.order_id,
    orders.amount
FROM customers
LEFT JOIN orders ON customers.customer_id = orders.customer_id;

結果:

nameorder_idamount
田中太郎1015000
佐藤花子1023000
鈴木一郎NULLNULL

鈴木一郎さんも結果に含まれます

どちらを使うべき?

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;

構文の説明:

  1. FROM customers:左側のテーブル(基準となる)
  2. LEFT JOIN orders:右側のテーブル(結合する)
  3. 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_idname
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_idnameorder_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;

結合の順序:

  1. customersとordersを結合
  2. その結果と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 NULLIS 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

実践のヒント:

  1. まず必要なデータを整理する
  2. どのテーブルを基準(左側)にするか決める
  3. 結合条件を正しく設定する
  4. NULL値の処理を忘れずに
  5. 必要な列だけをSELECTする

LEFT JOINを使えば、「全顧客の購入履歴(購入していない人も含む)」「全商品の販売実績(売れていない商品も含む)」といった、包括的なレポートが作成できます。

実際のプロジェクトで何度も使って、体で覚えていきましょう。最初は難しく感じても、すぐに慣れるはずです!

コメント

タイトルとURLをコピーしました