JOINの仕組みを理解する
JOIN はどうやって動いているか
テーブルを結合する JOIN? 複数のテーブルを結合して一つの結果セットにする操作。INNER JOIN(両方に存在する行のみ)・LEFT JOIN(左テーブルの全行 + 一致した右テーブル)などがある。 は、SQLの中で最もよく使われる機能のひとつです。しかし「なんとなく使えている」状態では、複雑なクエリを書いたときやパフォーマンス問題が起きたときに対処できません。まずは内部アルゴリズムを理解しましょう。
JOINの内部アルゴリズム
Nested Loop Join(ネステッドループ結合)
最もシンプルなアルゴリズムです。外側テーブルの各行に対して、内側テーブルを全走査して一致する行を探します。
for each row in table_A:
for each row in table_B:
if join_condition is true:
output (row_A, row_B)
小さいテーブル同士や、内側テーブルに適切なインデックスがある場合に効率的です。インデックスがある場合、内側の走査がO(log n)になります。
Hash Join(ハッシュ結合)
大きなテーブル同士の結合で使われます。まず一方のテーブルをハッシュテーブルに読み込み、もう一方を走査しながらハッシュテーブルを参照します。
-- Phase 1: Build
for each row in table_B:
hash_table[join_key] = row
-- Phase 2: Probe
for each row in table_A:
match = hash_table[row.join_key]
if match exists:
output (row, match)
インデックスがなくても高速で、大量データの結合に向いています。ただしハッシュテーブル生成のためのメモリが必要です。
結合タイプの全体像
説明のために次の2つのテーブルを使います。
-- ordersテーブル
| order_id | customer_id | amount |
|----------|-------------|--------|
| 1 | 101 | 3000 |
| 2 | 102 | 1500 |
| 3 | 999 | 800 | -- 存在しない顧客
-- customersテーブル
| customer_id | name |
|-------------|--------|
| 101 | 田中 |
| 102 | 佐藤 |
| 103 | 鈴木 | -- 注文なし
INNER JOIN
INNER JOIN? 結合条件が一致する行だけを返すJOIN。両テーブルに対応するデータが存在する場合のみ結果に含まれる。最もよく使われる結合方法。 は両方のテーブルに一致するレコードだけを返します。最も一般的な結合です。
SELECT o.order_id, c.name, o.amount
FROM orders o
INNER JOIN customers c ON o.customer_id = c.customer_id;
| order_id | name | amount |
|----------|------|--------|
| 1 | 田中 | 3000 |
| 2 | 佐藤 | 1500 |
-- order_id=3(顧客999)と customer_id=103(鈴木)は結果に含まれない
LEFT JOIN
LEFT JOIN? 左テーブルの全行を返し、右テーブルに一致するデータがない場合はNULLで補うJOIN。「注文がないユーザーも含めて一覧表示したい」といった用途に使う。 は左テーブルの全行と、右テーブルの一致した行を返します。一致しない場合は右側がNULLになります。
SELECT o.order_id, c.name, o.amount
FROM orders o
LEFT JOIN customers c ON o.customer_id = c.customer_id;
| order_id | name | amount |
|----------|------|--------|
| 1 | 田中 | 3000 |
| 2 | 佐藤 | 1500 |
| 3 | NULL | 800 | -- 顧客が存在しないのでNULL
RIGHT JOIN
RIGHT JOIN は右テーブルの全行を保持します。LEFT JOIN を使って LEFT / RIGHT を入れ替えたものと等価なので、実務では LEFT JOIN に統一するチームが多いです。
SELECT o.order_id, c.name, o.amount
FROM orders o
RIGHT JOIN customers c ON o.customer_id = c.customer_id;
| order_id | name | amount |
|----------|------|--------|
| 1 | 田中 | 3000 |
| 2 | 佐藤 | 1500 |
| NULL | 鈴木 | NULL | -- 注文なし
FULL OUTER JOIN
両テーブルの全行を返します。一致しない側はNULLになります。MySQLでは直接サポートされていないため、LEFT JOIN と RIGHT JOIN を UNION で組み合わせる必要があります。
-- PostgreSQL / SQL Server
SELECT o.order_id, c.name
FROM orders o
FULL OUTER JOIN customers c ON o.customer_id = c.customer_id;
-- MySQL での代替
SELECT o.order_id, c.name FROM orders o
LEFT JOIN customers c ON o.customer_id = c.customer_id
UNION
SELECT o.order_id, c.name FROM orders o
RIGHT JOIN customers c ON o.customer_id = c.customer_id;
CROSS JOIN(デカルト積)
ON 句を持たず、全行の組み合わせを生成します。行数は テーブルA行数 × テーブルB行数 になります。
SELECT a.color, b.size
FROM colors a
CROSS JOIN sizes b;
-- colors が3行、sizes が4行なら12行返る
カレンダーテーブルの生成や、テストデータの組み合わせ生成などに活用されます。
SELF JOIN(自己結合)
同じテーブルを2回 JOIN します。組織の階層構造(上司・部下の関係)や、同じテーブル内で行同士を比較したいときに使います。
-- 社員と上司を同じテーブルから取得
SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.employee_id;
ON と USING の違い
JOIN の結合条件を指定する方法は2つあります。
-- ON: 列名が異なっていても使える(一般的)
SELECT * FROM orders o
JOIN customers c ON o.customer_id = c.customer_id;
-- USING: 両テーブルで列名が同じ場合に使える(簡潔)
SELECT * FROM orders
JOIN customers USING (customer_id);
USING を使うと結果の列に customer_id が1列だけ含まれます(ON の場合は両テーブル分の列が含まれます)。列名が統一されているならUSINGの方がすっきり書けます。
複数テーブルをJOINする際の注意点
3テーブル以上を結合するときは、結合の順序と行の増減に注意が必要です。
SELECT o.order_id, c.name, p.product_name
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.product_id;
行の増加に注意: 1対多の関係でJOINすると行が増えます。1つの注文に3つの商品がある場合、その注文の行は3行になります。意図せず COUNT(*) の結果がズレることがあるので注意してください。
NULL の伝播: LEFT JOIN の後に INNER JOIN を続けると、LEFT JOIN で生成された NULL 行が INNER JOIN の条件を満たせず除外されることがあります。結合の順序を意識して設計しましょう。
まとめ
- JOIN の内部アルゴリズムには Nested Loop Join と Hash Join がある
- INNER JOIN: 両テーブルに一致する行のみ
- LEFT JOIN: 左テーブルの全行を保持(一致しない右側はNULL)
- FULL OUTER JOIN: 両テーブルの全行を保持
- CROSS JOIN: デカルト積(全組み合わせ)
- SELF JOIN: 同じテーブルを2回結合して階層や比較に使う
- 複数JOIN時は行の増加と NULL の伝播に注意
次回は サブクエリ? SQL文の中に入れ子になった別のSELECT文。WHERE句・FROM句・SELECT句などで使用できる。「平均より多い注文をしたユーザー」のような複雑な条件に使う。 の種類と使い分けを解説します。