【SQL】JOIN完全ガイド|INNER・LEFT・RIGHT・FULL・CROSS・自己結合・ON vs USING・3テーブル結合まで解説

【SQL】JOIN完全ガイド|INNER・LEFT・RIGHT・FULL・CROSS・自己結合・ON vs USING・3テーブル結合まで解説 SQL

リレーショナルデータベースでは、データを複数のテーブルに分けて管理します。JOIN は、関連するテーブルを横に結合して一つの結果として取り出す操作です。

この記事では INNER JOIN・LEFT JOIN・RIGHT JOIN・FULL JOIN の 4 種類を基本から解説し、さらに CROSS JOIN(直積)・自己結合・3 テーブル以上の結合・ONUSING の使い分け・パフォーマンス注意点まで体系的にまとめます。

スポンサーリンク

JOIN とは何か

JOIN は複数のテーブルを列(横)方向につなげて、1 つの結果セットとして扱う操作です。テーブルを行(縦)方向に連結する UNION と区別してください。

種類 結合の方向 概要
INNER JOIN 横(列) 両テーブルに一致するデータのみ取得
LEFT JOIN 横(列) 左テーブル全件 + 右テーブルの一致データ
RIGHT JOIN 横(列) 右テーブル全件 + 左テーブルの一致データ
FULL JOIN 横(列) 両テーブルの全件(一致しない側は NULL)
CROSS JOIN 横(列) すべての組み合わせ(直積)
UNION 縦(行) 複数の SELECT 結果を縦に連結
以降の例で使用するサンプルデータ:
departments(部門)テーブル 4 件(「経営」は従業員なし)、employees(従業員)テーブル 6 件(「渡辺」は部門未割当で department_id が NULL)で解説します。
サンプルデータ(以降の例で使用)
-- 部門テーブル(departments)
-- id | name
--  1 | 営業
--  2 | 開発
--  3 | 人事
--  4 | 経営(従業員なし)

-- 従業員テーブル(employees)
-- id | name         | department_id | salary
--  1 | 田中 太郎    |             1 | 55000
--  2 | 鈴木 花子    |             2 | 70000
--  3 | 佐藤 一郎    |             1 | 48000
--  4 | 高橋 美咲    |             2 | 80000
--  5 | 伊藤 健二    |             3 | 52000
--  6 | 渡辺 さくら  |          NULL | 65000  -- 部門未割当

INNER JOIN(内部結合)

INNER JOIN は両テーブルで結合条件に一致した行だけを返します。どちらかのテーブルに対応するデータがない行は結果から除外されます。

INNER JOIN の基本
-- 従業員と所属部門を結合して取得
SELECT
    e.name           AS 氏名,
    d.name           AS 部門,
    e.salary         AS 給与
FROM employees AS e
INNER JOIN departments AS d ON e.department_id = d.id
ORDER BY e.id;

-- 結果(部門未割当の「渡辺」と、従業員のいない「経営」はどちらも除外される):
-- 氏名          | 部門 | 給与
-- -----------+------+-------
-- 田中 太郎  | 営業 | 55000
-- 鈴木 花子  | 開発 | 70000
-- 佐藤 一郎  | 営業 | 48000
-- 高橋 美咲  | 開発 | 80000
-- 伊藤 健二  | 人事 | 52000
INNER JOIN のポイント:

  • JOIN だけ書いた場合は INNER JOIN と同じ動作(INNER は省略可能)
  • 結合条件(ON 句)に一致しない行は両テーブルともに結果から除外される
  • NULL 値は等号(=)で一致しないため、department_id が NULL の行は除外される

LEFT JOIN(左外部結合)

LEFT JOIN は左テーブルの全行を必ず返し、右テーブルに一致するデータがない場合は NULL を補完します。「一方のテーブルのデータは必ず全件欲しい」場面で多用します。

LEFT JOIN の基本
-- 従業員を全員取得し、部門情報があれば結合する
SELECT
    e.name           AS 氏名,
    d.name           AS 部門,
    e.salary         AS 給与
FROM employees AS e
LEFT JOIN departments AS d ON e.department_id = d.id
ORDER BY e.id;

-- 結果(「渡辺」は部門未割当だが LEFT JOIN で残り、部門列は NULL):
-- 氏名            | 部門   | 給与
-- --------------+--------+-------
-- 田中 太郎      | 営業   | 55000
-- 鈴木 花子      | 開発   | 70000
-- 佐藤 一郎      | 営業   | 48000
-- 高橋 美咲      | 開発   | 80000
-- 伊藤 健二      | 人事   | 52000
-- 渡辺 さくら    | NULL   | 65000  ← 左テーブル(employees)の行が残る
LEFT JOIN + IS NULL で「存在しないデータ」を検出する
-- 部門が未割当の従業員だけを取得(LEFT JOIN + IS NULL パターン)
SELECT e.name, e.salary
FROM employees AS e
LEFT JOIN departments AS d ON e.department_id = d.id
WHERE d.id IS NULL;

-- 結果:
-- 氏名            | 給与
-- 渡辺 さくら    | 65000
片方のテーブルにしか存在しないデータの取得:
LEFT JOIN + IS NULL / NOT EXISTS / NOT IN の使い分けは片方のテーブルに存在しないデータを取得する方法で詳しく解説しています。

RIGHT JOIN(右外部結合)

RIGHT JOIN は LEFT JOIN の逆で、右テーブルの全行を必ず返し、左テーブルに一致データがない場合は NULL を補完します。LEFT JOIN でテーブルの順番を入れ替えれば同じ結果になるため、RIGHT JOIN を使う機会は少なめです。

RIGHT JOIN の基本
-- 部門を全件取得し、所属する従業員情報を結合する
SELECT
    d.name           AS 部門,
    e.name           AS 氏名,
    e.salary         AS 給与
FROM employees AS e
RIGHT JOIN departments AS d ON e.department_id = d.id
ORDER BY d.id, e.id;

-- 結果(従業員のいない「経営」部門が NULL で残る):
-- 部門   | 氏名            | 給与
-- ------+----------------+-------
-- 営業   | 田中 太郎      | 55000
-- 営業   | 佐藤 一郎      | 48000
-- 開発   | 鈴木 花子      | 70000
-- 開発   | 高橋 美咲      | 80000
-- 人事   | 伊藤 健二      | 52000
-- 経営   | NULL           | NULL   ← 右テーブル(departments)の行が残る
RIGHT JOIN は LEFT JOIN で書き直せる
-- 上記の RIGHT JOIN と同じ結果をLEFT JOINで書く
SELECT
    d.name           AS 部門,
    e.name           AS 氏名,
    e.salary         AS 給与
FROM departments AS d           -- テーブルの順番を入れ替える
LEFT JOIN employees AS e ON e.department_id = d.id
ORDER BY d.id, e.id;
RIGHT JOIN 使用上の注意:
チームのコーディング規約によっては RIGHT JOIN を禁止して LEFT JOIN に統一することもあります。RIGHT JOIN は FROM 節のテーブル順を入れ替えると LEFT JOIN に変換できるため、可読性のために統一するのは合理的です。

FULL JOIN(完全外部結合)

FULL JOIN(FULL OUTER JOIN)は両テーブルの全行を返し、一致しない側の列は NULL になります。「どちらのテーブルにしか存在しないデータも含め全件確認したい」場面で使います。

MySQL は FULL JOIN に非対応:
MySQL / MariaDB は FULL JOIN をサポートしていません。FULL JOIN が必要な場合は LEFT JOIN と RIGHT JOIN を UNION ALL で結合して代替します(後述)。PostgreSQL・SQL Server・Oracle は FULL (OUTER) JOIN を直接使用できます。
FULL JOIN の基本(PostgreSQL / SQL Server / Oracle)
-- 従業員と部門の両方を全件取得(一致しない側は NULL)
SELECT
    e.name           AS 氏名,
    d.name           AS 部門
FROM employees AS e
FULL JOIN departments AS d ON e.department_id = d.id
ORDER BY d.id, e.id;

-- 結果:
-- 氏名            | 部門
-- 田中 太郎      | 営業
-- 佐藤 一郎      | 営業
-- 鈴木 花子      | 開発
-- 高橋 美咲      | 開発
-- 伊藤 健二      | 人事
-- 渡辺 さくら    | NULL   ← 部門未割当(employees にしかいない)
-- NULL            | 経営   ← 従業員なし(departments にしかない)
MySQL での FULL JOIN 代替(LEFT JOIN UNION ALL RIGHT JOIN)
-- MySQL では FULL JOIN の代わりに LEFT JOIN + UNION ALL + RIGHT JOIN を使う
SELECT e.name AS 氏名, d.name AS 部門
FROM employees AS e
LEFT JOIN departments AS d ON e.department_id = d.id

UNION ALL

SELECT e.name AS 氏名, d.name AS 部門
FROM employees AS e
RIGHT JOIN departments AS d ON e.department_id = d.id
WHERE e.id IS NULL;  -- LEFT JOINで既に取得した行の重複を防ぐ

CROSS JOIN(直積・クロス結合)

CROSS JOIN は結合条件を持たず、左テーブルの各行と右テーブルの全行を組み合わせた直積を返します。左テーブル M 行 × 右テーブル N 行 = M×N 行の結果になります。

CROSS JOIN の使用例
-- サイズ(S/M/L)× カラー(赤/青)の全組み合わせを生成
-- sizes: 3行, colors: 2行 → 結果: 6行
SELECT s.size_name, c.color_name
FROM sizes   AS s
CROSS JOIN colors AS c;

-- 結果:
-- size_name | color_name
-- ----------+-----------
-- S         | 赤
-- S         | 青
-- M         | 赤
-- M         | 青
-- L         | 赤
-- L         | 青

-- 注意: 大きなテーブルに CROSS JOIN すると結果が爆発的に増える
-- 100万行 × 100万行 = 1兆行(メモリ・時間ともに危険)
CROSS JOIN は意図せず発生することがある:
FROM 句で複数テーブルをカンマ区切りで並べ、ON 句(結合条件)を書き忘れると CROSS JOIN と同じ動作になります。例: FROM employees, departments WHERE ... の WHERE を忘れると全組み合わせが返ります。JOIN を書く場合は必ず ON 句か USING 句を添えてください。

自己結合(SELF JOIN)

自己結合は同一テーブルを別名(エイリアス)で 2 つ使って結合する技法です。階層構造(上司と部下、親カテゴリと子カテゴリなど)の表現によく使われます。

自己結合の例(上司と部下)
-- employees テーブルに manager_id 列があるとする
-- manager_id は同じテーブルの id を参照する(NULL = 管理職なし)
SELECT
    e.name         AS 従業員,
    m.name         AS 上司
FROM employees AS e
LEFT JOIN employees AS m ON e.manager_id = m.id
ORDER BY e.id;

-- 結果:
-- 従業員          | 上司
-- 田中 太郎      | NULL         ← 上司なし(管理職)
-- 鈴木 花子      | 田中 太郎
-- 佐藤 一郎      | 田中 太郎
-- 高橋 美咲      | NULL         ← 上司なし(管理職)
-- 伊藤 健二      | 高橋 美咲
自己結合のポイント:

  • 同じテーブルを e(従業員)と m(上司)など、別々のエイリアスで参照する
  • 上司がいない行も取得したいときは INNER JOIN ではなく LEFT JOIN を使う
  • 階層が深い(祖先〜子孫)場合は再帰 CTE(WITH RECURSIVE)が有効。詳細はWITH句完全ガイドを参照

3 テーブル以上の結合

JOIN は何度でも連鎖できます。結合した結果をさらに別のテーブルと結合する形で記述します。

3 テーブルの結合例
-- テーブル構成:
-- orders(注文): order_id, customer_id, product_id, qty
-- customers(顧客): customer_id, customer_name
-- products(商品): product_id, product_name, unit_price

SELECT
    o.order_id,
    c.customer_name  AS 顧客名,
    p.product_name   AS 商品名,
    o.qty            AS 数量,
    o.qty * p.unit_price AS 金額
FROM orders     AS o
INNER JOIN customers AS c ON o.customer_id = c.customer_id
INNER JOIN products  AS p ON o.product_id  = p.product_id
ORDER BY o.order_id;
複数テーブル結合のコツ:

  • FROM の直後に「軸となるテーブル」を置き、JOIN を連鎖させていく
  • 各 JOIN に ON 句を漏らさず記述する(漏れると CROSS JOIN になる)
  • 複雑なクエリは WITH 句(CTE)でステップに分けると可読性が上がる
  • JOIN の順序はオプティマイザが最適化することが多いが、ヒント句で制御できる RDBMS もある

ON と USING の使い分け

JOIN の結合条件を書く方法は ONUSING の 2 種類があります。両テーブルで列名が同じ場合は USING で短く書けます。

ON USING
対応 RDBMS すべて MySQL・PostgreSQL・Oracle など(SQL Server は非対応)
列名が異なる場合 使える(ON e.dept_id = d.id 使えない(列名が同一でないと NG)
列名が同じ場合 両テーブル名を書く必要がある 列名だけ書けば OK
SELECT * の挙動 結合列が両テーブル分出力される 結合列は 1 列だけ出力される
ON と USING の比較
-- ON: 列名が異なっても使える(汎用的)
SELECT e.name, d.name AS 部門
FROM employees AS e
INNER JOIN departments AS d ON e.department_id = d.id;

-- USING: 両テーブルで同一列名の場合に短く書ける
-- (employees.department_id と departments.department_id が同名の場合)
SELECT e.name, d.name AS 部門
FROM employees AS e
INNER JOIN departments AS d USING (department_id);

-- USING を使うと SELECT * での重複列が除かれる
-- ON では department_id が両テーブルから2列出る
-- USING では department_id は1列だけ出る

JOIN のパフォーマンス注意点

注意点 詳細
結合列にインデックスを貼る ON / USING に指定する列(外部キー・参照先 PK)にはインデックスが必須。なければフルスキャンになる
絞り込みは早めに行う JOIN する前に WHERE や サブクエリで行を減らすと結合対象が減り高速になる
CROSS JOIN に注意 意図しない CROSS JOIN(ON 句の書き忘れ)で結果が爆発する。EXPLAIN で実行計画を必ず確認
SELECT * を避ける JOIN すると列数が増えるため、必要な列だけ明示的に指定する
NULL の扱い ON 句の列が NULL の場合は一致しない。NULL を含む列で結合するときは COALESCE や IS NULL を活用
EXPLAIN で実行計画を確認する(MySQL)
-- JOINのパフォーマンスを確認するには EXPLAIN(実行計画)を使う
EXPLAIN
SELECT e.name, d.name AS 部門
FROM employees AS e
INNER JOIN departments AS d ON e.department_id = d.id;

-- type列が "ALL" はフルスキャン(インデックス不使用)→ インデックスを追加
-- type列が "ref" や "eq_ref" は効率よくインデックスが使われている

よくある質問(FAQ)

QINNER JOIN と WHERE で絞り込むのは何が違いますか?
AINNER JOIN の ON 句と WHERE 句はどちらも行の絞り込みに使えますが、意味と可読性が異なります。ON e.department_id = d.id は「結合条件(テーブルをつなぐキー)」を表し、WHERE d.name = '開発' は「結合後に絞り込む業務条件」を表します。特に LEFT JOIN では ON 句と WHERE 句の書き場所が結果に影響します:WHERE に書くと一致しない NULL 行が除外されて実質 INNER JOIN になります。
QLEFT JOIN の ON 句と WHERE 句の場所を間違えると結果が変わりますか?
Aはい、変わります。右テーブルの条件を WHERE に書くと、NULL になった行が WHERE で除外されて INNER JOIN と同じ結果になります。右テーブルへの絞り込み条件は ON 句に書くのが正しいです。例:LEFT JOIN departments AS d ON e.department_id = d.id AND d.name = '開発'(ON に書く)とLEFT JOIN departments AS d ON e.department_id = d.id WHERE d.name = '開発'(WHERE に書く)は結果が異なります。前者は「開発部門以外の従業員も全員残り、部門列が NULL になる」、後者は「開発部門の従業員だけになる」です。
Q同じ結果なら LEFT JOIN と RIGHT JOIN どちらを使うべきですか?
Aチームの好みや規約によりますが、一般的には LEFT JOIN に統一することを推奨するチームが多いです。FROM の直後に「軸となる主テーブル」を置き、LEFT JOIN で補助テーブルを結合するパターンの方が読みやすいためです。RIGHT JOIN は FROM 節のテーブルを入れ替えれば LEFT JOIN で同じ結果を得られます。
QJOIN すると行数が増えてしまうことがありますか?
Aあります。右テーブルに同じキーの行が複数ある場合(1対多の関係)、左テーブルの1行が複数行に展開されて結果行数が増えます。例:1人の従業員が複数のプロジェクトに参加していると、JOIN 後に従業員行がプロジェクト数分に増えます。集計(SUM・COUNT など)の前に行数が意図通りかを SELECT COUNT(*) で確認してください。重複を除くには DISTINCT や GROUP BY を使います。
QNATURAL JOIN はどんな場合に使えますか?
ANATURAL JOIN は両テーブルで同名の列すべてを自動的に結合条件にする構文です。ON 句や USING 句を省略できますが、「どの列で結合されているか」が見えにくく、テーブル定義が変わったとき(同名列が増えたなど)に意図しない挙動になるリスクがあります。実務では可読性のために USINGON を明示する方が安全です。

まとめ

JOIN の種類 取得できるデータ 主な用途
INNER JOIN 両テーブルで一致した行のみ 関連データが確実に存在する場合の通常の結合
LEFT JOIN 左テーブル全件 + 右の一致行(なければ NULL) 「存在しないかもしれない」補助データを結合する場合
RIGHT JOIN 右テーブル全件 + 左の一致行(なければ NULL) LEFT JOIN のテーブル順を入れ替えれば代替可能
FULL JOIN 両テーブルの全件(一致しない側は NULL) 両テーブルに存在しないデータも含めて全件確認
CROSS JOIN すべての組み合わせ(M×N 行) マスタ×オプションの全パターン生成など
自己結合 同テーブルを 2 つのエイリアスで結合 上司−部下・親−子カテゴリなど階層構造

JOIN を使いこなすことでデータベース設計の正規化(テーブル分割)を活かした柔軟なデータ取得ができます。特定テーブルにしか存在しないデータを取る方法は片方のテーブルに存在しないデータを取得する方法で、複雑な集計クエリの整理にはWITH 句(CTE)完全ガイドも活用してください。