更新: 2026年03月 · 8 分で読める · 4,168 文字
SQLのJOIN 4種類を業務別に使い分けるための実践ガイド
SQLのJOIN操作は複数のテーブルを組み合わせる際の基本スキルです。この記事では、INNER JOIN、LEFT JOIN、RIGHT JOIN、FULL OUTER JOINの4種類について、それぞれの動作原理と実際の業務シーンでの使い分けを実装例付きで解説します。正しいJOIN種別を選択することで、クエリのパフォーマンスと結果の正確性が大きく改善されます。
JOINの基本概念:なぜ複数のテーブルを結合するのか
多くのデータベースは正規化の原則に基づいて設計され、関連データが複数のテーブルに分散しています。例えば、顧客情報テーブルと注文テーブルが別々に存在する場合、「顧客Aの全注文を取得する」という要件を満たすにはJOIN操作が必須です。
JOINを使用しないと、アプリケーション層でループ処理を行う必要が生じ、パフォーマンスが低下します。データベースレベルで結合を行うことが最適解です。
INNER JOIN:両テーブルに存在するデータのみを抽出
動作原理と特徴
INNER JOINは、結合条件を満たすレコードのみを返します。両テーブルの交差部分を取得するイメージです。最も一般的で頻繁に使用されるJOIN種別です。
使用場面:完全にマッチするデータのペアのみが必要な場合。例えば、注文が確定した顧客情報のみを取得する際に使用します。
注意点:結合キーが存在しないレコードは自動的に除外されます。意図せずデータが失われないか確認が必要です。
-- 顧客テーブルと注文テーブルをINNER JOINで結合
SELECT
c.customer_id,
c.customer_name,
o.order_id,
o.order_date,
o.amount
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id
WHERE o.order_date >= '2024-01-01'
ORDER BY o.order_date DESC;
上記のクエリでは、注文が存在する顧客のみが結果に含まれます。注文のない顧客はフィルタされます。
LEFT JOIN(LEFT OUTER JOIN):左テーブルの全データを保持
動作原理と特徴
LEFT JOINは、左側のテーブルにある全レコードを返します。右側のテーブルにマッチするデータがない場合、右側のカラムはNULLで埋められます。
使用場面:関連データがない場合も含めて、左テーブルの全データを把握する必要がある場合。例えば、「注文を出していない顧客」を特定する際に活用します。
ハマりポイント:NULLチェックが必須です。集計関数を使う場合、COUNT(o.order_id)ではなくCOUNT(CASE WHEN o.order_id IS NOT NULL THEN 1 END)のように明示的に条件を指定する必要があります。
-- 全顧客と、存在すれば最新注文情報を取得
SELECT
c.customer_id,
c.customer_name,
c.registration_date,
o.order_id,
o.order_date,
COALESCE(o.amount, 0) as amount -- NULLの場合は0を返す
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
AND o.order_date = (
-- 各顧客の最新注文日のみを結合
SELECT MAX(order_date)
FROM orders
WHERE customer_id = c.customer_id
)
ORDER BY c.customer_id;
このクエリにより、購入実績のない顧客も含めた全顧客リストが取得できます。未購入顧客の量は、マーケティング施策の対象設定に重要な情報です。
RIGHT JOIN(RIGHT OUTER JOIN):右テーブルの全データを保持
動作原理と特徴
RIGHT JOINはLEFT JOINの逆操作です。右側のテーブルの全レコードを返し、左側でマッチしなかったデータはNULLで埋められます。
使用場面:実務ではLEFT JOINで十分な場合が多いため、RIGHT JOINの出番は限定的です。ただし、テーブルの順序を入れ替えたくない設計思想がある場合に使用します。
推奨事項:可読性の観点から、RIGHT JOINが必要な場合はテーブルの順序を入れ替えてLEFT JOINを使う方が一般的です。
-- RIGHT JOINの例(使用は非推奨)
SELECT
c.customer_id,
c.customer_name,
o.order_id
FROM customers c
RIGHT JOIN orders o ON c.customer_id = o.customer_id;
-- 同等の推奨記述
SELECT
c.customer_id,
c.customer_name,
o.order_id
FROM orders o
LEFT JOIN customers c ON c.customer_id = o.customer_id;
下記の例の方が意図が明確で、チームメンバーも理解しやすくなります。
FULL OUTER JOIN:両テーブルの全データを保持
動作原理と特徴
FULL OUTER JOINは、両テーブルの全レコードを返します。マッチしなかったデータ両側ともNULLで埋められます。結果セットは他のJOINより大きくなる傾向があります。
使用場面:データの差分検出や、両テーブル間の不整合を特定する場合に活用します。MySQLではFULL OUTER JOINがサポートされていないため、UNIONを使った代替実装が必要です。
対応状況:PostgreSQL、SQL Server、Oracleではネイティブサポート。MySQLではUNIONとLEFT/RIGHT JOINを組み合わせて実装します。
-- PostgreSQL/SQL Serverでの実装
SELECT
COALESCE(c.customer_id, o.customer_id) as customer_id,
c.customer_name,
o.order_id,
o.order_date
FROM customers c
FULL OUTER JOIN orders o ON c.customer_id = o.customer_id
ORDER BY customer_id;
-- MySQLでの代替実装(UNIONを使用)
SELECT
c.customer_id,
c.customer_name,
o.order_id,
o.order_date
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
UNION
SELECT
o.customer_id,
c.customer_name,
o.order_id,
o.order_date
FROM customers c
RIGHT JOIN orders o ON c.customer_id = o.customer_id
WHERE c.customer_id IS NULL;
MySQLのUNION実装では、下記のSELECT文でWHERE句を付けることで、重複行を避けています。
実務における使い分けのチェックリスト
どのJOINを選ぶべきか判断基準
| 要件 | 推奨JOIN | 理由 |
|---|---|---|
| 両テーブルでマッチするデータのみ必要 | INNER JOIN | 確実なデータペアのみを抽出 |
| 左テーブルの全データが必須 | LEFT JOIN | 関連データがなくてもレコードを保持 |
| 両テーブルの不整合を検出 | FULL OUTER JOIN | データベース間のギャップを特定 |
| マスタデータと取引データの結合 | LEFT JOIN | マスタデータの完全性を保証 |
パフォーマンス最適化のポイント
JOIN句での条件指定
結合条件はON句に記載し、WHERE句では使用しないようにしましょう。特にLEFT JOINではこれが重要です。
-- 悪い例:WHERE句での条件指定でLEFT JOINが実質INNER JOINになる
SELECT c.customer_id, o.order_id
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
WHERE o.order_date >= '2024-01-01'; -- NULLの行も除外される
-- 良い例:ON句に条件を含める
SELECT c.customer_id, o.order_id
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
AND o.order_date >= '2024-01-01'; -- NULLの行を保持
インデックス戦略
結合キーとなるカラムにはインデックスを作成することが必須です。大規模テーブルの場合、インデックスがないと結合処理が非常に遅くなります。
-- 結合キーにインデックスを作成
CREATE INDEX idx_orders_customer_id ON orders(customer_id);
CREATE INDEX idx_customers_id ON customers(customer_id);
よくある質問
はい、可能です。JOIN句を連鎖させることで実現できます。
正常の可能性が高いです。LEFT JOINは右テーブルにマッチするデータがない場合、右テーブルのカラムをNULLで埋めます。COALESCE関数やCASE文を使ってNULLを適切な値に変換するのが一般的です。
CROSS JOINは2つのテーブルのすべての組み合わせ(直積)を返します。結合条件が不要で、結果セットサイズはTable1の行数 × Table2の行数になります。実務ではめったに使いません。
まとめ
- INNER JOINは両テーブルでマッチするデータのみ返す。最も一般的で安全な選択
- LEFT JOINは左テーブル全体を保持し、実務で最頻出。マスタデータと取引データの結合に最
おすすめデータベースリソース
- PostgreSQL Documentation Comprehensive PostgreSQL manual with SQL reference.
- Redis Documentation Redis command reference and best practices.
- Use The Index, Luke Classic guide to SQL index design. Free to read online.