Oracle の JOIN は、複数のテーブルを結合して1つの結果セットとして扱う機能です。SQL の中でも最も基本かつ重要な機能であり、適切な JOIN の選択がクエリのパフォーマンスを大きく左右します。
Oracle には SQL 標準の JOIN 構文に加えて、古くから使われてきた (+) 構文による外部結合もあります。現在のプロジェクトでは多くの場合、両方の書き方が混在しているため、読み書きの両方を理解しておくことが重要です。
この記事でわかること
- INNER JOIN / CROSS JOIN の基本と使い方
- LEFT / RIGHT / FULL OUTER JOIN と NULL の扱い
- Oracle 独自の (+) 外部結合構文(旧来の書き方)
- NATURAL JOIN と JOIN USING の違い
- 自己結合(SELF JOIN)で階層・比較を行うパターン
- 3テーブル以上の結合パターンと結合順序の考え方
- カルテシアン積(意図しない CROSS JOIN)の防止
- Nested Loop / Hash Join / Sort Merge Join の使い分け
JOIN の種類と結果の違い
次のサンプルデータを前提に各 JOIN の結果を示します。
| departments(部門) | employees(従業員) | ||
|---|---|---|---|
| dept_id | dept_name | emp_id | dept_id |
| 10 | 総務部 | 1001 | 10 |
| 20 | 人事部 | 1002 | 10 |
| 30 | 購買部 | 1003 | 20 |
| 1004 | NULL(部門未所属) |
| JOIN 種類 | 返す行 | 主な用途 |
|---|---|---|
| INNER JOIN | 両方に一致する行のみ | マスターと明細の一致データ取得 |
| LEFT OUTER JOIN | 左テーブルの全行 + 右テーブルの一致行(なければ NULL) | マスター全件 + 紐付く明細(欠損もNULLで表示) |
| RIGHT OUTER JOIN | 右テーブルの全行 + 左テーブルの一致行(なければ NULL) | LEFT OUTER の逆(テーブル順を入れ替えて LEFT で代用可能) |
| FULL OUTER JOIN | 両テーブルの全行(一致しない側は NULL) | 差分検出・突合チェック |
| CROSS JOIN | 両テーブルの全組み合わせ(件数の積) | カレンダー生成・組み合わせ表作成 |
INNER JOIN:両テーブルに一致する行だけを返す
INNER JOIN の基本
-- ANSI 構文(推奨): JOIN = INNER JOIN(デフォルト)
SELECT e.employee_id,
e.last_name,
d.department_name,
e.salary
FROM employees e
INNER JOIN departments d
ON e.department_id = d.department_id
ORDER BY e.employee_id;
-- 省略形(INNER は省略可)
SELECT e.employee_id, e.last_name, d.department_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id;
-- 旧来の Oracle 構文(WHERE 句での等値結合)
-- 現在は ANSI 構文の使用を推奨するが、既存コードで見かけることが多い
SELECT e.employee_id, e.last_name, d.department_name
FROM employees e, departments d
WHERE e.department_id = d.department_id;
テーブルエイリアスは必ず付ける
複数テーブルを結合する場合、
複数テーブルを結合する場合、
e.employee_id のようにテーブルエイリアスを使って列を明示することで、同名列の曖昧さを防ぎ、クエリの可読性が上がります。
LEFT / RIGHT / FULL OUTER JOIN
LEFT OUTER JOIN:左テーブルの全行を返す(部門未所属の従業員も含む)
-- LEFT OUTER JOIN: employees の全行 + 一致する departments(なければ NULL)
SELECT e.employee_id,
e.last_name,
d.department_name -- 部門未所属の従業員は NULL になる
FROM employees e
LEFT OUTER JOIN departments d
ON e.department_id = d.department_id
ORDER BY e.employee_id;
-- 結果:
-- EMP_ID LAST_NAME DEPT_NAME
-- 1001 田中 総務部
-- 1002 佐藤 総務部
-- 1003 鈴木 人事部
-- 1004 高橋 NULL ← 部門未所属でも返る
-- LEFT OUTER も省略形で LEFT JOIN と書ける
SELECT e.employee_id, e.last_name, d.department_name
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id;
FULL OUTER JOIN:差分検出・突合チェックに使う
-- FULL OUTER JOIN: 両テーブルの全行を返す(一致しない側は NULL)
-- 活用例: マスターと実績の差分チェック
SELECT
COALESCE(m.product_id, a.product_id) AS product_id,
m.product_name,
a.actual_sales,
CASE
WHEN m.product_id IS NULL THEN 'マスターに未登録'
WHEN a.product_id IS NULL THEN '実績データなし'
ELSE '正常'
END AS status
FROM products m
FULL OUTER JOIN actual_sales a
ON m.product_id = a.product_id
ORDER BY 1;
Oracle 独自の (+) 外部結合構文
Oracle 9i 以前から使われてきた外部結合の書き方です。現在の開発では ANSI 構文(LEFT/RIGHT OUTER JOIN)の使用を推奨しますが、既存システムのコードや古いドキュメントで頻繁に見かけるため、読めるようにしておく必要があります。
(+) 構文の読み方と ANSI 構文への変換
-- (+) は「NULL を補完する側」に付ける(外部結合を受ける側) -- LEFT OUTER JOIN の (+) 構文 -- departments に (+) → departments 側に一致しない行は NULL で補完 SELECT e.employee_id, e.last_name, d.department_name FROM employees e, departments d WHERE e.department_id = d.department_id(+); -- (+) は departments 側 -- ANSI 同等: employees LEFT OUTER JOIN departments -- RIGHT OUTER JOIN の (+) 構文 -- employees に (+) → employees 側に一致しない行は NULL で補完 SELECT e.employee_id, e.last_name, d.department_name FROM employees e, departments d WHERE e.department_id(+) = d.department_id; -- (+) は employees 側 -- ANSI 同等: employees RIGHT OUTER JOIN departments -- (!) FULL OUTER JOIN は (+) 構文では書けない(ANSI 構文が必要) -- (!) (+) 構文と ANSI JOIN 構文を同じクエリで混在させると ORA-25156 が発生
| ANSI 構文 | (+) 構文の位置 | 直感的な覚え方 |
|---|---|---|
| A LEFT OUTER JOIN B | A.col = B.col(+) |
データが欠ける(NULL になる)B 側に (+) |
| A RIGHT OUTER JOIN B | A.col(+) = B.col |
データが欠ける(NULL になる)A 側に (+) |
| FULL OUTER JOIN | 書けない | ANSI 構文を使う |
CROSS JOIN:全組み合わせを生成する
CROSS JOIN:カレンダー・組み合わせ表の生成
-- CROSS JOIN: テーブルA の全行 × テーブルB の全行
-- 3行 × 4行 = 12行が返る
-- 活用例1: 全社員 × 全月 のカレンダー骨格を作る
SELECT e.employee_id,
m.month_no,
TO_CHAR(LAST_DAY(ADD_MONTHS(TRUNC(SYSDATE, 'YYYY'), m.month_no - 1)), 'YYYY-MM') AS year_month
FROM employees e
CROSS JOIN (
SELECT LEVEL AS month_no FROM DUAL CONNECT BY LEVEL <= 12
) m
ORDER BY e.employee_id, m.month_no;
-- 活用例2: サイズ × カラーの全組み合わせ表
SELECT s.size_name, c.color_name
FROM sizes s
CROSS JOIN colors c
ORDER BY s.size_name, c.color_name;
意図しない CROSS JOIN(カルテシアン積)に注意
WHERE 句の結合条件を書き忘れると、全行 × 全行 のカルテシアン積が発生し、大量の行が返って処理が極端に遅くなります。
WHERE 句の結合条件を書き忘れると、全行 × 全行 のカルテシアン積が発生し、大量の行が返って処理が極端に遅くなります。
FROM employees, departments と書いて結合条件を忘れた場合がこれにあたります。
NATURAL JOIN と JOIN USING
JOIN USING:同名列をキーに結合する
-- JOIN USING: 指定した列名が両テーブルで一致する場合に使う -- ON 句より簡潔に書けるが、同名列が複数ある場合に誤結合するリスクがある SELECT employee_id, last_name, department_name FROM employees JOIN departments USING (department_id) -- 両テーブルに department_id がある ORDER BY employee_id; -- NATURAL JOIN: 同名の列をすべてキーとして自動的に INNER JOIN する -- 列の追加・削除で意図せず動作が変わるリスクがあるため、実務での使用は非推奨 SELECT employee_id, last_name, department_name FROM employees NATURAL JOIN departments;
自己結合(SELF JOIN):同一テーブルを複数回結合する
自己結合:従業員とその上司名を同時に取得する
-- 自己結合: 同じテーブルに別名を付けて結合する
-- 活用例: employees テーブルで各従業員とその manager の名前を取得
SELECT e.employee_id,
e.last_name AS employee_name,
m.employee_id AS manager_id,
m.last_name AS manager_name
FROM employees e
LEFT JOIN employees m -- 上司は同じテーブルの別行
ON e.manager_id = m.employee_id
ORDER BY e.employee_id;
-- LEFT JOIN: manager_id が NULL(最上位のトップ)の従業員も含む
-- 給与比較: 同じ部門で自分より給与が高い従業員の数を調べる
SELECT e.employee_id,
e.last_name,
e.salary,
COUNT(h.employee_id) AS higher_count
FROM employees e
LEFT JOIN employees h
ON e.department_id = h.department_id
AND e.salary < h.salary -- 同じ部門で自分より給与が高い
WHERE e.department_id = 50
GROUP BY e.employee_id, e.last_name, e.salary
ORDER BY e.salary DESC;
深い階層(多段階の上司関係など)を再帰的にたどりたい場合は、自己結合よりも CONNECT BY 階層問い合わせや Oracle 11g R2 以降で使える再帰 WITH 句(再帰 CTE)の方が適しています。
3テーブル以上の結合パターン
3テーブル結合:従業員・部門・職種を同時に取得
-- 複数テーブルは JOIN を重ねて書く
SELECT e.employee_id,
e.last_name,
d.department_name,
j.job_title,
l.city AS office_city
FROM employees e
JOIN departments d ON e.department_id = d.department_id
JOIN jobs j ON e.job_id = j.job_id
JOIN locations l ON d.location_id = l.location_id
WHERE d.department_id IN (10, 20, 50)
ORDER BY d.department_id, e.employee_id;
-- OUTER JOIN を混在させる場合は結合の方向に注意
-- 部門未所属の従業員も含め、部門がある場合は所在地も表示する
SELECT e.employee_id,
e.last_name,
d.department_name,
l.city
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id
LEFT JOIN locations l ON d.location_id = l.location_id
ORDER BY e.employee_id;
非等値結合(範囲条件での結合)
非等値結合:給与レンジテーブルと結合する
-- 等値(=)以外の条件で結合する
-- 活用例: 給与額に対応するグレードを salary_grades テーブルから求める
-- salary_grades テーブル構造:
-- GRADE LOW_SAL HIGH_SAL
-- A 700000 9999999
-- B 500000 699999
-- C 300000 499999
-- D 0 299999
SELECT e.employee_id,
e.last_name,
e.salary,
sg.grade AS salary_grade
FROM employees e
JOIN salary_grades sg
ON e.salary BETWEEN sg.low_sal AND sg.high_sal
ORDER BY e.salary DESC;
-- 旧来の Oracle 構文(WHERE 句での非等値結合)
SELECT e.employee_id, e.last_name, e.salary, sg.grade
FROM employees e, salary_grades sg
WHERE e.salary BETWEEN sg.low_sal AND sg.high_sal
ORDER BY e.salary DESC;
JOIN のパフォーマンス:実行計画の読み方
Oracle はクエリの実行計画で、JOIN に使うアルゴリズムを自動的に選択します。主な3種類のアルゴリズムを理解しておくと、パフォーマンス問題の診断に役立ちます。
| アルゴリズム | 動作 | 向いているケース |
|---|---|---|
| Nested Loop Join | 外側テーブルの各行に対して内側テーブルをループ検索 | 外側が少行でインデックスが使える場合。OLTP の PK 結合 |
| Hash Join | 小さいテーブルをハッシュ表に変換し大きいテーブルとマッチング | 大量データの等値結合。インデックスなしでも高速 |
| Sort Merge Join | 両テーブルをソートしてマージ | 非等値結合・既にソート済みのデータ |
実行計画の確認と JOIN ヒント
-- EXPLAIN PLAN で実行計画を確認する EXPLAIN PLAN FOR SELECT e.employee_id, d.department_name FROM employees e JOIN departments d ON e.department_id = d.department_id; SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY); -- Plan: -- NESTED LOOPS -- TABLE ACCESS FULL DEPARTMENTS -- TABLE ACCESS BY INDEX ROWID EMPLOYEES -- INDEX RANGE SCAN EMP_DEPT_IDX -- ヒント句でアルゴリズムを強制する(USE_NL, USE_HASH, USE_MERGE) SELECT /*+ USE_HASH(e d) */ e.employee_id, d.department_name FROM employees e JOIN departments d ON e.department_id = d.department_id; -- 結合順序をヒントで指定する(LEADING) SELECT /*+ LEADING(d e) USE_NL(e) */ e.employee_id, d.department_name FROM employees e JOIN departments d ON e.department_id = d.department_id;
JOIN でよくある間違いと対処
| パターン | 症状 | 対処 |
|---|---|---|
| WHERE 句の結合条件漏れ | カルテシアン積が発生して件数が膨大になる | すべてのテーブルペアに結合条件を書く。ANSI JOIN に移行 |
| LEFT JOIN 後に WHERE で NULL 除外 | LEFT JOIN が実質 INNER JOIN になる | NULL を許容する条件は ON 句に書く(WHERE ではなく) |
| (+) と ANSI JOIN の混在 | ORA-25156: (+)構文とANSI構文の混在は不可 | どちらか一方に統一する |
| 同名列の曖昧さ | ORA-00918: 列の定義が曖昧です | テーブルエイリアスを付けて「e.department_id」のように明示 |
LEFT JOIN 後の WHERE で NULL 除外になるパターン(NG/OK)
-- NG: LEFT JOIN したのに WHERE で right テーブルの列を絞ると INNER JOIN と同じ結果になる
SELECT e.employee_id, d.department_name
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id
WHERE d.location_id = 1700; -- NULL の行が除外されて INNER JOIN と同等に!
-- OK: LEFT JOIN 先の絞り込みは ON 句に書く
SELECT e.employee_id, d.department_name
FROM employees e
LEFT JOIN departments d
ON e.department_id = d.department_id
AND d.location_id = 1700; -- ON 句: NULL の行も残る
まとめ
- INNER JOIN:両テーブルに一致する行のみ。最も一般的
- LEFT OUTER JOIN:左テーブルの全行を返す。右側は一致しなければ NULL
- FULL OUTER JOIN:両テーブルの全行。差分検出・突合チェックに使う
- (+) 構文:旧来の Oracle 外部結合。読めるようにしておく。新規開発は ANSI 構文を使う
- 自己結合:同じテーブルを別名で複数回使う。上司・部下関係の表示に便利
- LEFT JOIN 後の WHERE:右テーブルの列を WHERE で絞ると INNER JOIN になる。絞り込みは ON 句に書く
- 実行計画は
DBMS_XPLAN.DISPLAYで確認し、必要に応じて JOIN ヒントで制御する
SQL ヒント句によるJOIN アルゴリズムの制御についてはOracle SQLヒント句の使い方完全ガイドを、階層データを再帰的にたどる場合はOracle 階層問い合わせ(CONNECT BY)完全ガイドも参照してください。

