リレーショナルデータベースでは、データを複数のテーブルに分けて管理します。JOIN は、関連するテーブルを横に結合して一つの結果として取り出す操作です。
この記事では INNER JOIN・LEFT JOIN・RIGHT JOIN・FULL JOIN の 4 種類を基本から解説し、さらに CROSS JOIN(直積)・自己結合・3 テーブル以上の結合・ON と USING の使い分け・パフォーマンス注意点まで体系的にまとめます。
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 は両テーブルで結合条件に一致した行だけを返します。どちらかのテーブルに対応するデータがない行は結果から除外されます。
-- 従業員と所属部門を結合して取得
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
JOINだけ書いた場合はINNER JOINと同じ動作(INNER は省略可能)- 結合条件(ON 句)に一致しない行は両テーブルともに結果から除外される
- NULL 値は等号(=)で一致しないため、department_id が NULL の行は除外される
LEFT JOIN(左外部結合)
LEFT JOIN は左テーブルの全行を必ず返し、右テーブルに一致するデータがない場合は NULL を補完します。「一方のテーブルのデータは必ず全件欲しい」場面で多用します。
-- 従業員を全員取得し、部門情報があれば結合する
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 パターン) 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 を使う機会は少なめです。
-- 部門を全件取得し、所属する従業員情報を結合する
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で書く
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 を禁止して LEFT JOIN に統一することもあります。RIGHT JOIN は FROM 節のテーブル順を入れ替えると LEFT JOIN に変換できるため、可読性のために統一するのは合理的です。
FULL JOIN(完全外部結合)
FULL JOIN(FULL OUTER JOIN)は両テーブルの全行を返し、一致しない側の列は NULL になります。「どちらのテーブルにしか存在しないデータも含め全件確認したい」場面で使います。
MySQL / MariaDB は FULL JOIN をサポートしていません。FULL JOIN が必要な場合は LEFT JOIN と RIGHT JOIN を
UNION ALL で結合して代替します(後述)。PostgreSQL・SQL Server・Oracle は FULL (OUTER) JOIN を直接使用できます。-- 従業員と部門の両方を全件取得(一致しない側は 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 を使う 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 行の結果になります。
-- サイズ(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兆行(メモリ・時間ともに危険)
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 は何度でも連鎖できます。結合した結果をさらに別のテーブルと結合する形で記述します。
-- テーブル構成:
-- 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 の結合条件を書く方法は ON と USING の 2 種類があります。両テーブルで列名が同じ場合は USING で短く書けます。
ON |
USING |
|
|---|---|---|
| 対応 RDBMS | すべて | MySQL・PostgreSQL・Oracle など(SQL Server は非対応) |
| 列名が異なる場合 | 使える(ON e.dept_id = d.id) |
使えない(列名が同一でないと NG) |
| 列名が同じ場合 | 両テーブル名を書く必要がある | 列名だけ書けば OK |
| SELECT * の挙動 | 結合列が両テーブル分出力される | 結合列は 1 列だけ出力される |
-- 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 を活用 |
-- 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)
ON e.department_id = d.id は「結合条件(テーブルをつなぐキー)」を表し、WHERE d.name = '開発' は「結合後に絞り込む業務条件」を表します。特に LEFT JOIN では ON 句と WHERE 句の書き場所が結果に影響します:WHERE に書くと一致しない NULL 行が除外されて実質 INNER JOIN になります。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 になる」、後者は「開発部門の従業員だけになる」です。SELECT COUNT(*) で確認してください。重複を除くには DISTINCT や GROUP BY を使います。NATURAL JOIN は両テーブルで同名の列すべてを自動的に結合条件にする構文です。ON 句や USING 句を省略できますが、「どの列で結合されているか」が見えにくく、テーブル定義が変わったとき(同名列が増えたなど)に意図しない挙動になるリスクがあります。実務では可読性のために USING か ON を明示する方が安全です。まとめ
| JOIN の種類 | 取得できるデータ | 主な用途 |
|---|---|---|
INNER JOIN |
両テーブルで一致した行のみ | 関連データが確実に存在する場合の通常の結合 |
LEFT JOIN |
左テーブル全件 + 右の一致行(なければ NULL) | 「存在しないかもしれない」補助データを結合する場合 |
RIGHT JOIN |
右テーブル全件 + 左の一致行(なければ NULL) | LEFT JOIN のテーブル順を入れ替えれば代替可能 |
FULL JOIN |
両テーブルの全件(一致しない側は NULL) | 両テーブルに存在しないデータも含めて全件確認 |
CROSS JOIN |
すべての組み合わせ(M×N 行) | マスタ×オプションの全パターン生成など |
| 自己結合 | 同テーブルを 2 つのエイリアスで結合 | 上司−部下・親−子カテゴリなど階層構造 |
JOIN を使いこなすことでデータベース設計の正規化(テーブル分割)を活かした柔軟なデータ取得ができます。特定テーブルにしか存在しないデータを取る方法は片方のテーブルに存在しないデータを取得する方法で、複雑な集計クエリの整理にはWITH 句(CTE)完全ガイドも活用してください。

