| この記事の要点 |
|
JOIN の基本
SQL の JOIN は、複数テーブルを条件に従って組み合わせ、1つの結果セットを得る構文です。FROM A JOIN B ON A.x = B.x は等号結合の例で、等号以外の条件も使えます。
Oracle Database 19cを軸に、他製品の例を比較として区別します。次の複数行VALUESはMySQL/PostgreSQL等の比較用で、19cにはそのまま使えません。19cでは INSERT INTO employees VALUES (1, 'Alice', 10); のように各行を単行INSERTに分け、両表の計6行を扱います。CREATE/INSERTは説明用データを作る変更操作です。後続の結果コメントはこの3行ずつの入力からの期待値で、実測ログやORDER BYのない表示順の保証ではありません。
-- サンプル
CREATE TABLE employees (id INT, name VARCHAR(50), dept_id INT);
CREATE TABLE departments (id INT, name VARCHAR(50));
INSERT INTO employees VALUES
(1,'Alice',10), (2,'Bob',20), (3,'Carol',NULL);
INSERT INTO departments VALUES
(10,'Sales'), (20,'Engineering'), (30,'Marketing');
5 種類の JOIN
| JOIN 種類 | 左にのみ存在 | 両方に存在 | 右にのみ存在 |
|---|---|---|---|
| INNER JOIN | − | ○ | − |
| LEFT [OUTER] JOIN | ○(右側 NULL) | ○ | − |
| RIGHT [OUTER] JOIN | − | ○ | ○(左側 NULL) |
| FULL OUTER JOIN | ○ | ○ | ○ |
| CROSS JOIN | 全組合せ(デカルト積) | ||
INNER JOIN
SELECT e.name, d.name AS dept
FROM employees e
INNER JOIN departments d ON e.dept_id = d.id;
-- 結果
-- Alice | Sales
-- Bob | Engineering
-- (Carol は dept_id=NULL なので除外、Marketing は employee なしで除外)
LEFT OUTER JOIN
SELECT e.name, d.name AS dept
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.id;
-- 結果
-- Alice | Sales
-- Bob | Engineering
-- Carol | NULL ← 左に居る人は必ず出る
RIGHT OUTER JOIN
SELECT e.name, d.name AS dept
FROM employees e
RIGHT JOIN departments d ON e.dept_id = d.id;
-- 結果
-- Alice | Sales
-- Bob | Engineering
-- NULL | Marketing ← 右に居る部署は必ず出る
FULL OUTER JOIN
FULL OUTER JOINは一致した組合せと左右の未一致行を返します。MySQL 8.4にはこの構文がありません。下の旧比較枠後半のLEFT UNION RIGHTは投影結果の重複を消すため、一般にはFULL JOINの行多重度を保ちません。単にUNION ALLへ替えるだけでも一致行を二重に数えます。
次は同じ e.dept_id = d.id 条件に限定したMySQL向け訂正形です。左結合全行に右未一致行だけを足します。この等号で一致する行のe.dept_idは必ず非NULLなので、右側枝の未一致判定に使えます。条件を変える場合は判定列も見直します。
SELECT e.name, d.name AS dept
FROM employees e LEFT JOIN departments d ON e.dept_id = d.id
UNION ALL
SELECT e.name, d.name AS dept
FROM employees e RIGHT JOIN departments d ON e.dept_id = d.id
WHERE e.dept_id IS NULL;
以下は元の製品別比較枠です。前半のFULL構文と、後半の重複排除を伴う旧UNION例を区別してください。
-- PostgreSQL / Oracle / SQL Server
SELECT e.name, d.name AS dept
FROM employees e
FULL OUTER JOIN departments d ON e.dept_id = d.id;
-- MySQL は LEFT UNION RIGHT で代替
SELECT e.name, d.name FROM employees e LEFT JOIN departments d ON e.dept_id = d.id
UNION
SELECT e.name, d.name FROM employees e RIGHT JOIN departments d ON e.dept_id = d.id;
CROSS JOIN (デカルト積)
SELECT e.name, d.name FROM employees e CROSS JOIN departments d;
-- 3 employees × 3 departments = 9 行
-- 古典的記法(カンマ)でも同じ
SELECT e.name, d.name FROM employees e, departments d;
SELF JOIN
同じテーブルを自分自身と結合。階層 / 上司部下関係などで活躍。
次の例はemployeesにmanager_id列がある別の教材です。冒頭DDLにはmanager_idがないため、その表のままでは実行できません。
-- 社員と上司
SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;
USING 句
結合キーが両表で同じカラム名のとき、USING (col) で簡潔に等号結合できます。SELECT *でまとまるのはUSINGに指定した列だけで、他の同名列まで統合されません。SQL ServerはこのUSING構文に対応しません。
次の「両表にdept_idがある場合」という前提が必要です。冒頭departmentsの列はid/nameだけなので、そのままの表には使えません。冒頭の表なら既出の ON e.dept_id = d.id を使います。
-- 両表に dept_id がある場合
SELECT * FROM employees JOIN departments USING (dept_id);
-- ON e.dept_id = d.dept_id と同じだが
-- 結果に dept_id が 1 列だけ出る(ON だと 2 列)
NATURAL JOIN
両表で同名カラム全てを自動結合。意図しない結合が起きるため避けるのがベストプラクティス。
次の例を冒頭の定義で読むと共通列はid/nameで、dept_idとidの結合にはなりません。created_atのコメントは、両表にその列がある別の場合への注意です。
SELECT * FROM employees NATURAL JOIN departments;
-- 両表に created_at があると、それも結合キーに使われて事故る
複数 JOIN
次はorders/customers/order_items/productsの別教材で、参照する全列がある前提です。OracleのDATE型created_atなら境界は o.created_at >= DATE '2026-06-01' とし、下の文字列比較をNLS非依存の共通日時構文とは扱いません。
SELECT o.id, c.name AS customer, p.name AS product, oi.quantity
FROM orders o
JOIN customers c ON o.customer_id = c.id
JOIN order_items oi ON oi.order_id = o.id
JOIN products p ON oi.product_id = p.id
WHERE o.created_at >= '2026-06-01';
SEMI JOIN / ANTI JOIN
「片方に存在するか / しないか」を判定して左表の行だけ返す JOIN。EXISTS / NOT EXISTS で表現。
下のLEFT JOIN版の o.id IS NULL は、一致したorders行のidが必ず非NULLという前提が必要です。idがNULLを許すなら一致行も拾います。掲載の o.customer_id = c.id という等号条件に限定すれば、訂正形は WHERE o.customer_id IS NULL です。
-- SEMI JOIN: 注文がある顧客
SELECT * FROM customers c
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id);
-- ANTI JOIN: 注文が一度もない顧客
SELECT * FROM customers c
WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id);
-- LEFT JOIN + IS NULL でも同じ表現可(ANTI JOIN)
SELECT c.*
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.id IS NULL;
ON と WHERE の違い (OUTER JOIN)
OUTER JOINでは、フィルタを ON に書くか WHERE に書くかで意味が変わります。下の d.active = 1 はNULL補完行をWHEREで落としますが、すべてのWHERE条件が外部結合を内部結合と同じにするわけではありません。通常のINNER JOINで同じ条件をON/WHERE間で移す場合とは区別します。
次の例はdepartmentsにactive列がある別教材です。冒頭DDLにはactiveがないため、そのままの表では実行できません。
-- 例: アクティブな部署のみを左結合したいが
-- 部署が無い社員は残したい
-- ✅ ON に書く: Carol(部署無し) は残る
SELECT e.name, d.name
FROM employees e
LEFT JOIN departments d
ON e.dept_id = d.id AND d.active = 1;
-- ❌ WHERE に書く: NULL がフィルタで弾かれ Carol が消える
SELECT e.name, d.name
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.id
WHERE d.active = 1; -- d.active が NULL の Carol は除外される
Join Algorithm (Optimizer の選択)
| アルゴリズム | 得意な場面 | コストの確認点(固定計算量ではない) |
|---|---|---|
| Nested Loop Join | 外側の対象が少なく、内側を効率的に探索できる場合 | 外側の件数×内側へのアクセス。索引の有無・一致件数で変わる |
| Hash Join | 等号結合で大量行を扱う場合など。索引がないことは必須条件ではない | 入力行数・メモリ・ディスクへの退避・出力件数で変わる |
| Sort-Merge Join | 順序付きの入力を利用できる場合など。必要ならソートする | ソートの要否・入力/出力件数・メモリで変わる |
| Index Nested Loop | 内側の索引を利用するNested Loop。PK以外も候補 | 索引アクセスと表アクセス、一致する行数を確認 |
次は製品別の省略断片で、実行済みログではありません。PostgreSQLのEXPLAIN ANALYZEは対象文を実行します。MySQLの通常EXPLAINとは別です。OracleのEXPLAIN PLANは計画表へ書き込み、DBMS_XPLAN.DISPLAYはその計画を表示します。SQL ServerのSTATISTICS PROFILE ONは後続の実行に作用し、単独では計画を表示しません。
-- 実行計画の確認
EXPLAIN ANALYZE SELECT ... -- PostgreSQL
EXPLAIN SELECT ... -- MySQL
EXPLAIN PLAN FOR SELECT ...; SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY); -- Oracle
SET STATISTICS PROFILE ON; -- SQL Server
性能のコツ
- 結合キーのインデックスは候補。既存索引・選択性・更新コストと計画を確認し、一律に追加しない
- SQLに表を書く順序だけでNLJの内外や速度は決まらない。結合条件の制約とオプティマイザが選んだ実際の順序を確認する
- 絞込み位置は計画で確認する。WHEREに書けば物理的に先に処理されるとは限らず、外部結合ではONとの移動で結果も変わり得る
- 不要な列を SELECT しない(カバーリングインデックス効果)
- 統計情報の状態を確認する。収集方法は製品別で、Oracleの統計収集はDBMS_STATSを使う。ANALYZE/UPDATE STATISTICSを製品共通の自動対処にしない
注文・顧客・商品の別教材
以下はemployees/departmentsの例とは独立した列契約の教材です。ordersにはorder_id/customer_id/amount/product_id、customersにはid/name、productsにはid/product_nameがある前提で、「複数 JOIN」節のorder_items経由・products.nameの例とも混ぜません。SQLの操作分類はOracle SQLのDML、SQL全体の分類と主要RDBMSの方言は総合的なSQLの解説を参照してください。
注文と顧客のINNER JOIN
注文と顧客が条件に一致する組合せを選びます。
SELECT o.order_id, c.name, o.amount
FROM orders o
INNER JOIN customers c
ON o.customer_id = c.id;
顧客を残すLEFT JOIN
顧客を左に置き、対応する注文がない顧客も残します。
SELECT c.name, o.order_id
FROM customers c
LEFT JOIN orders o
ON c.id = o.customer_id;
注文・顧客・商品の3表結合
この教材はorders.product_idからproducts.idへ直接結合し、product_nameを選びます。
SELECT o.order_id, c.name, p.product_name
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id
INNER JOIN products p ON o.product_id = p.id;
カンマ表記と明示INNER JOINの対照
次の2文は同じ列と等号条件を使った内部結合の対照です。条件をまとめて読みやすくするには明示JOINを使えますが、表記だけで速度の優劣は決まりません。行の抽出条件はWHERE句、結合結果を集約した後の条件はHAVING句で区別します。
SELECT o.order_id, c.name
FROM orders o, customers c
WHERE o.customer_id = c.id;
SELECT o.order_id, c.name
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id;
FAQ
Q: RIGHT JOIN はいつ使う?
A: ほぼ使いません。FROM B RIGHT JOIN A は FROM A LEFT JOIN B と等価で、可読性のため LEFT に統一するのが慣習。
Q: INNER JOIN ON 1=1 は何?
A: 結合条件「常に真」= CROSS JOIN。デカルト積を作りたいときに書きますが、意図を明確にするため CROSS JOIN 構文を使う方が良いです。
Q: NULL は JOIN でどう扱われる?
A: 通常の等号条件では NULL = NULL はUNKNOWNで一致しません。NULL同士も一致させる要件なら、Oracleでは ON (a.k = b.k OR (a.k IS NULL AND b.k IS NULL)) のように明示できます。PostgreSQLの IS NOT DISTINCT FROM は別製品の比較例です。NVLで同じ代替値へ置換すると実在値との衝突や型変換があるため、一律の代案にはしません。
公式仕様の確認先:Oracle19c SELECT、MySQL8.4集合演算、PostgreSQL EXPLAIN(2026-10-28確認)。画面・設定・コードは対象版と各例の前提条件を照合してください。
子ページはありません
人気ページ
- 1 Eclipseで「サーバーに追加または除去できるリソースがありません。」の原因と対処法
- 2 tomcat の起動 / 停止ログと catalina.log・catalina.out の違い
- 3 JavaScript で base URL を取得する方法|window.location.origin
- 4 YouTube Data API v3 エラー一覧|403・400・404 の原因と対処
- 5 Laravel エラー一覧|500/Blade/DB 接続/ルーティングの代表エラー
- 6 3Dグラフィックスのソフト一覧|Blender・Metasequoiaと用途別の選び方
- 7 Spring Frameworkのアノテーション一覧
- 8 【Spring】@Valueアノテーションとは
- 9 CATALINA_HOME の確認方法 (Linux / Mac)
- 10 【Spring】@Autowiredアノテーションとは
最近更新/作成されたページ
- Visual Studio「デザイナーを表示できません」エラーの原因と対処 2026-10-28 12:06:00
- SSH 認証に失敗しました。再試行してください の対処 2026-10-28 12:06:00
- SSH2 秘密鍵の読み込みに失敗しました の原因と対処 2026-10-28 12:06:00
- TeraTerm のよくあるエラーと対処法完全ガイド — Connection refused / 2026-10-28 12:06:00
- Visual Studio エラー一覧|コンパイルエラー・警告の確認方法と対処 2026-10-28 11:39:57
- Whitelabel Error Page の原因と消し方|Spring Boot のエラーページ 2026-10-28 11:39:57
- Eclipseで「サーバーに追加または除去できるリソースがありません。」の原因と対処法 2026-10-28 11:39:57
- SQL WHERE 句完全ガイド 2026-10-28 11:39:57
- SQL JOIN 完全ガイド (INNER/LEFT/RIGHT/FULL OUTER) 2026-10-28 11:39:57
- tomcat の起動 / 停止ログと catalina.log・catalina.out の違い 2026-10-28 10:24:23
- Tomcat の起動確認方法 5 つ|localhost:8080・ポート・プロセス・ログ 2026-10-28 10:24:23
- Oracle 19c COMMITの使い方: 暗黙コミット・自動確定・応答不明時の確認 2026-10-28 09:59:09
- Oracle 19c DELETE文|削除対象の確認・COMMIT前の判断・ROLLBACKの条件 2026-10-28 09:59:09
- Oracle 19c INSERT文|INSERT ALL・SELECT・RETURNINGとMERGEの使い分け 2026-10-28 09:59:09
- Oracle 19cのトランザクション制御|COMMIT・ROLLBACK・分離レベルと接続設定 2026-10-28 09:59:09