【Oracle】JOIN完全ガイド|INNER/OUTER/CROSS JOIN・Oracle独自の(+)構文・自己結合・3テーブル結合・パフォーマンス最適化まで実例で解説

【Oracle】JOIN完全ガイド|INNER/OUTER/CROSS JOIN・Oracle独自の(+)構文・自己結合・3テーブル結合・パフォーマンス最適化まで実例で解説 Oracle

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 句の結合条件を書き忘れると、全行 × 全行 のカルテシアン積が発生し、大量の行が返って処理が極端に遅くなります。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)完全ガイドも参照してください。