SQLのJOINを基礎から解説!複数テーブルを結合する方法と使い分け

データベース・SQL

実務のデータベースでは、1つのテーブルだけで完結することは少なく、複数のテーブルから情報を組み合わせて取得する場面が頻繁にあります。

そんな時に欠かせないのが「JOIN(ジョイン)」です。

この記事では、SQLのJOIN構文を初心者向けに解説し、よく使うJOINの種類や活用例を紹介します。

スポンサーリンク

JOINってなに?

JOINの役割

JOINは、共通するカラムをもとに複数のテーブルを結び付けて1つの結果セットにするSQLの仕組みです。

身近な例で理解しよう

例えば、ECサイトを考えてみましょう。

usersテーブル(顧客情報)

id | name     | email
---|----------|------------------
1  | 田中太郎  | tanaka@example.com
2  | 佐藤花子  | sato@example.com
3  | 山田次郎  | yamada@example.com

ordersテーブル(注文情報)

id | user_id | order_date | amount
---|---------|------------|-------
1  | 1       | 2025-06-01 | 2500
2  | 1       | 2025-06-02 | 1800
3  | 2       | 2025-06-03 | 3200

この2つのテーブルから「誰がいつ注文したか」を知りたい時、JOINを使います。

基本的な書き方

SELECT users.name, orders.order_date, orders.amount
FROM users
JOIN orders ON users.id = orders.user_id;

結果のイメージ

name     | order_date | amount
---------|------------|-------
田中太郎  | 2025-06-01 | 2500
田中太郎  | 2025-06-02 | 1800
佐藤花子  | 2025-06-03 | 3200

ポイント

  • 共通するカラム(user_idとid)で関連付け
  • 複数のテーブルの情報を1つの結果で取得
  • 「ON」で結合条件を指定する

まずはJOINの役割を理解することがスタートです。次は、最もよく使う「INNER JOIN」の書き方を見てみましょう。

INNER JOIN(内部結合)を覚えよう

INNER JOINの特徴

INNER JOINは、両方のテーブルに共通するデータだけを取得します。

最も基本的で使用頻度の高いJOINです。

基本的な書き方

SELECT カラム名 
FROM テーブルA
INNER JOIN テーブルB ON テーブルA.共通カラム = テーブルB.共通カラム;

実際の使用例

-- 社員と所属部署の情報を表示
SELECT 
    employees.name AS 社員名,
    departments.name AS 部署名
FROM employees
INNER JOIN departments ON employees.dept_id = departments.id;

-- 注文と顧客の情報を表示
SELECT 
    users.name AS 顧客名,
    orders.order_date AS 注文日,
    orders.amount AS 金額
FROM users
INNER JOIN orders ON users.id = orders.user_id;

結果の特徴

employeesテーブル

id | name     | dept_id
---|----------|--------
1  | 田中太郎  | 1
2  | 佐藤花子  | 2
3  | 山田次郎  | NULL

departmentsテーブル

id | name
---|-------
1  | 営業部
2  | 開発部
3  | 人事部

INNER JOINの結果

社員名   | 部署名
---------|-------
田中太郎  | 営業部
佐藤花子  | 開発部

山田次郎さんは部署が未設定(NULL)なので、結果に含まれません。

複数テーブルのINNER JOIN

-- 注文、顧客、商品の情報を結合
SELECT 
    users.name AS 顧客名,
    products.name AS 商品名,
    order_items.quantity AS 数量,
    orders.order_date AS 注文日
FROM orders
INNER JOIN users ON orders.user_id = users.id
INNER JOIN order_items ON orders.id = order_items.order_id
INNER JOIN products ON order_items.product_id = products.id;

ポイント

  • 両方のテーブルにデータがある行のみ表示
  • 「対応するデータが確実にある」場合に使用
  • JOINと書くだけでもINNER JOINの意味

INNER JOINは「対応するデータが両方にある時だけ欲しい」場合に使います。次は、片方にデータがないケースも含める「LEFT JOIN」を見てみましょう。

LEFT JOIN(左外部結合)を理解しよう

LEFT JOINの特徴

LEFT JOINは、左側のテーブルの全ての行を取得し、右側に対応するデータがあれば結合、なければNULLになります。

基本的な書き方

SELECT カラム名 
FROM テーブルA
LEFT JOIN テーブルB ON テーブルA.共通カラム = テーブルB.共通カラム;

実際の使用例

-- 全ユーザーと、その注文履歴を表示
SELECT 
    users.name AS 顧客名,
    orders.order_date AS 注文日,
    orders.amount AS 金額
FROM users
LEFT JOIN orders ON users.id = orders.user_id
ORDER BY users.name;

結果の比較

usersテーブル

id | name
---|--------
1  | 田中太郎
2  | 佐藤花子
3  | 山田次郎

ordersテーブル

id | user_id | order_date | amount
---|---------|------------|-------
1  | 1       | 2025-06-01 | 2500
2  | 2       | 2025-06-02 | 1800

LEFT JOINの結果

顧客名   | 注文日     | 金額
---------|------------|------
田中太郎  | 2025-06-01 | 2500
佐藤花子  | 2025-06-02 | 1800
山田次郎  | NULL       | NULL

山田次郎さんは注文履歴がないため、注文関連の情報がNULLになりますが、行は表示されます。

実用的な活用例

-- 注文のない顧客を特定
SELECT 
    users.name AS 顧客名,
    users.email AS メールアドレス
FROM users
LEFT JOIN orders ON users.id = orders.user_id
WHERE orders.user_id IS NULL;

-- 部署に所属していない社員を確認
SELECT 
    employees.name AS 社員名
FROM employees
LEFT JOIN departments ON employees.dept_id = departments.id
WHERE departments.id IS NULL;

-- 全顧客の注文回数を集計(注文のない顧客も含む)
SELECT 
    users.name AS 顧客名,
    COUNT(orders.id) AS 注文回数
FROM users
LEFT JOIN orders ON users.id = orders.user_id
GROUP BY users.id, users.name
ORDER BY 注文回数 DESC;

ポイント

  • 左側のテーブルの全行が結果に含まれる
  • 対応するデータがない場合はNULLで表示
  • 「全件表示+あれば関連情報も」の場合に使用

「全ユーザー一覧と、その注文があれば表示」といったケースに便利です。次は、逆の動きをする「RIGHT JOIN」です。

RIGHT JOIN(右外部結合)を知ろう

RIGHT JOINの特徴

RIGHT JOINは、右側のテーブルを基準にして、左側の一致するデータを結合します。

LEFT JOINの逆バージョンです。

基本的な書き方

SELECT カラム名 
FROM テーブルA
RIGHT JOIN テーブルB ON テーブルA.共通カラム = テーブルB.共通カラム;

実際の使用例

-- 全部署と、所属している社員を表示
SELECT 
    departments.name AS 部署名,
    employees.name AS 社員名
FROM employees
RIGHT JOIN departments ON employees.dept_id = departments.id
ORDER BY departments.name;

結果のイメージ

employeesテーブル

id | name     | dept_id
---|----------|--------
1  | 田中太郎  | 1
2  | 佐藤花子  | 2

departmentsテーブル

id | name
---|-------
1  | 営業部
2  | 開発部
3  | 人事部

RIGHT JOINの結果

部署名 | 社員名
-------|--------
営業部  | 田中太郎
開発部  | 佐藤花子
人事部  | NULL

人事部には社員がいないため、社員名がNULLになります。

LEFT JOINとの書き換え

RIGHT JOINは、LEFT JOINでテーブルの順序を入れ替えることで同じ結果が得られます。

-- RIGHT JOINの書き方
SELECT departments.name, employees.name
FROM employees
RIGHT JOIN departments ON employees.dept_id = departments.id;

-- 同じ結果をLEFT JOINで書く
SELECT departments.name, employees.name
FROM departments
LEFT JOIN employees ON departments.id = employees.dept_id;

注意点

  • MySQLなど一部のデータベースでのみサポート
  • PostgreSQLやSQLiteでは使用できない
  • LEFT JOINで代替可能なため、使用頻度は低い

ポイント

  • 右側のテーブルの全行が結果に含まれる
  • LEFT JOINの方が一般的で推奨される
  • どちらを使うかは好みやチーム内の規約による

LEFT JOINと同じく、NULLを含む結合が必要な時に使います。次は、両テーブルの全行を対象とするFULL JOINです。

FULL OUTER JOIN(全外部結合)を理解しよう

FULL OUTER JOINの特徴

FULL OUTER JOINは、左右両方のテーブルに存在する全ての行を取得し、どちらかにない場合はNULLで埋めます。

基本的な書き方

SELECT カラム名 
FROM テーブルA
FULL OUTER JOIN テーブルB ON テーブルA.共通カラム = テーブルB.共通カラム;

実際の使用例(PostgreSQL)

-- 全社員と全部署の情報を表示
SELECT 
    employees.name AS 社員名,
    departments.name AS 部署名
FROM employees
FULL OUTER JOIN departments ON employees.dept_id = departments.id;

結果のイメージ

employeesテーブル

id | name     | dept_id
---|----------|--------
1  | 田中太郎  | 1
2  | 佐藤花子  | 2
3  | 山田次郎  | NULL

departmentsテーブル

id | name
---|-------
1  | 営業部
2  | 開発部
3  | 人事部

FULL OUTER JOINの結果

社員名   | 部署名
---------|-------
田中太郎  | 営業部
佐藤花子  | 開発部
山田次郎  | NULL
NULL     | 人事部

MySQLでのFULL OUTER JOIN代替方法

MySQLはFULL OUTER JOINをサポートしていないため、UNION を使って代替します。

-- MySQLでの代替方法
SELECT employees.name, departments.name
FROM employees
LEFT JOIN departments ON employees.dept_id = departments.id

UNION

SELECT employees.name, departments.name
FROM employees
RIGHT JOIN departments ON employees.dept_id = departments.id;

実用的な活用例

-- 在籍状況と給与支払い状況の突合
SELECT 
    COALESCE(staff.name, payroll.name) AS 名前,
    staff.department AS 部署,
    payroll.salary AS 給与
FROM staff
FULL OUTER JOIN payroll ON staff.id = payroll.staff_id;

対応状況

  • PostgreSQL:サポートあり
  • SQL Server:サポートあり
  • Oracle:サポートあり
  • MySQL:サポートなし(UNIONで代替)
  • SQLite:サポートなし

ポイント

  • 両方のテーブルの全データを保持
  • データベースによってサポート状況が異なる
  • 完全な突合や差分チェックに使用

全データを保持したまま結合したい時に使います。最後に、JOINの使い分けと注意点を整理します。

JOINの使い分けと注意点

JOINの種類別比較

種類対象行NULL表示の有無使用場面
INNER JOIN両方に一致する行のみなし確実に関連がある場合
LEFT JOIN左テーブル全行+一致行右にない場合はNULL全件表示+関連情報
RIGHT JOIN右テーブル全行+一致行左にない場合はNULLLEFT JOINの逆
FULL OUTER JOIN両テーブルの全行一方にない場合はNULL完全な突合

よくある間違いと注意点

1. 結合条件を忘れる

-- ❌ 間違い:結合条件がない(カーティジアン積になる)
SELECT users.name, orders.order_date
FROM users, orders;

-- ✅ 正しい:ON句で結合条件を指定
SELECT users.name, orders.order_date
FROM users
INNER JOIN orders ON users.id = orders.user_id;

2. テーブル名の省略

-- ❌ あいまい:どちらのテーブルのnameか不明
SELECT name FROM users JOIN orders ON users.id = orders.user_id;

-- ✅ 明確:テーブル名を明記
SELECT users.name FROM users JOIN orders ON users.id = orders.user_id;

-- ✅ エイリアスを使用
SELECT u.name FROM users u JOIN orders o ON u.id = o.user_id;

3. NULLの扱いを理解していない

-- LEFT JOINでNULLをチェック
SELECT u.name
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.user_id IS NULL;  -- 注文のない顧客

パフォーマンスを向上させるコツ

インデックスの活用

-- 結合に使用する列にインデックスを作成
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_employees_dept_id ON employees(dept_id);

適切なJOINの選択

-- 必要なデータのみ取得
SELECT u.name, o.order_date
FROM users u
INNER JOIN orders o ON u.id = o.user_id
WHERE o.order_date >= '2025-06-01'  -- 条件を先に絞る
LIMIT 100;  -- 必要な件数のみ

複数JOINの実践例

-- 注文詳細レポート
SELECT 
    u.name AS 顧客名,
    o.order_date AS 注文日,
    p.name AS 商品名,
    oi.quantity AS 数量,
    oi.price AS 単価,
    (oi.quantity * oi.price) AS 小計
FROM users u
INNER JOIN orders o ON u.id = o.user_id
INNER JOIN order_items oi ON o.id = oi.order_id
INNER JOIN products p ON oi.product_id = p.id
WHERE o.order_date >= '2025-06-01'
ORDER BY o.order_date DESC, u.name;

ポイント

  • 目的に応じてJOINの種類を選択
  • 結合条件は必ず指定する
  • テーブル名やエイリアスで明確性を保つ
  • インデックスでパフォーマンスを向上

JOINは強力な機能ですが、正しい理解と構文で使いこなすことが重要です。

まとめ

SQLのJOINは、複数テーブルのデータを組み合わせて使いたい時に不可欠な構文です。

覚えるべきポイント

  • INNER JOIN:両方にデータがある行のみ取得
  • LEFT JOIN:左側の全行+対応する右側データ
  • RIGHT JOIN:右側の全行+対応する左側データ(使用頻度は低い)
  • FULL OUTER JOIN:両方の全行(MySQLは非対応)

実務での使い分け

INNER JOINを使う場面

  • 注文と顧客の関連情報
  • 社員と部署の確実な組み合わせ
  • 商品と在庫の関連データ

LEFT JOINを使う場面

  • 全顧客リスト(注文履歴の有無に関係なく)
  • 全社員リスト(部署配属の有無に関係なく)
  • マスタデータと実績データの組み合わせ

よく使われるパターン

-- 売上ランキング(顧客別)
SELECT 
    u.name,
    COUNT(o.id) AS 注文回数,
    SUM(o.amount) AS 合計金額
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name
ORDER BY 合計金額 DESC;

-- 在庫切れ商品の確認
SELECT p.name
FROM products p
LEFT JOIN inventory i ON p.id = i.product_id
WHERE i.stock IS NULL OR i.stock = 0;

コメント

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