はじめに
SQLの JOIN は、複数のテーブルを関連付けてデータを取得する機能です。図だけで暗記するより、結合条件と結果の行を実際に確認する方が理解しやすくなります。この記事はPostgreSQLの構文を基準に説明します。
練習用テーブルを作る
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name VARCHAR(30) NOT NULL
);
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
user_id INTEGER,
amount INTEGER NOT NULL
);
INSERT INTO users (id, name) VALUES
(1, '田中'), (2, '佐藤'), (3, '鈴木');
INSERT INTO orders (id, user_id, amount) VALUES
(101, 1, 3000), (102, 1, 1500), (103, 2, 2000), (104, 99, 500);
ここではJOINの違いを見せるため、orders.user_id=99 のような対応するユーザーがいないデータを意図的に含めています。本番システムなら外部キー制約などで整合性を確保するのが一般的です。
users
| id | name |
|---|---|
| 1 | 田中 |
| 2 | 佐藤 |
| 3 | 鈴木 |
orders
| id | user_id | amount |
|---|---|---|
| 101 | 1 | 3000 |
| 102 | 1 | 1500 |
| 103 | 2 | 2000 |
| 104 | 99 | 500 |
1. INNER JOIN:一致した行だけ
SELECT u.name, o.id AS order_id, o.amount
FROM users AS u
INNER JOIN orders AS o ON u.id = o.user_id
ORDER BY o.id;
| name | order_id | amount |
|---|---|---|
| 田中 | 101 | 3000 |
| 田中 | 102 | 1500 |
| 佐藤 | 103 | 2000 |
鈴木には注文がなく、注文104には対応するユーザーがないため結果に出ません。
2. LEFT JOIN:左側をすべて残す
SELECT u.name, o.id AS order_id, o.amount
FROM users AS u
LEFT JOIN orders AS o ON u.id = o.user_id
ORDER BY u.id, o.id;
| name | order_id | amount |
|---|---|---|
| 田中 | 101 | 3000 |
| 田中 | 102 | 1500 |
| 佐藤 | 103 | 2000 |
| 鈴木 | NULL | NULL |
LEFT JOIN では左側の users を残します。右側に該当行がなければ、その列が NULL になります。
3. RIGHT JOIN:右側をすべて残す
SELECT u.name, o.id AS order_id, o.amount
FROM users AS u
RIGHT JOIN orders AS o ON u.id = o.user_id
ORDER BY o.id;
| name | order_id | amount |
|---|---|---|
| 田中 | 101 | 3000 |
| 田中 | 102 | 1500 |
| 佐藤 | 103 | 2000 |
| NULL | 104 | 500 |
RIGHT JOIN はテーブルの左右を入れ替えた LEFT JOIN で表現できることも多いです。
4. FULL OUTER JOIN:両方の行を残す
SELECT u.name, o.id AS order_id, o.amount
FROM users AS u
FULL OUTER JOIN orders AS o ON u.id = o.user_id
ORDER BY o.id NULLS LAST, u.id;
| name | order_id | amount |
|---|---|---|
| 田中 | 101 | 3000 |
| 田中 | 102 | 1500 |
| 佐藤 | 103 | 2000 |
| NULL | 104 | 500 |
| 鈴木 | NULL | NULL |
FULL OUTER JOIN はPostgreSQLでは利用できますが、DB製品によっては直接サポートされません。
5. 1対多では行が増える
田中は注文を2つ持つので、JOIN結果でも2行になります。JOINした結果が増えたからといって、必ずしも重複データやSQLのバグとは限りません。
6. よくある落とし穴:WHERE句でLEFT JOINが絞り込まれる
-- 注文のない鈴木は結果から除外される
SELECT u.name, o.amount
FROM users AS u
LEFT JOIN orders AS o ON u.id = o.user_id
WHERE o.amount >= 2000;
右表の NULL 行は o.amount >= 2000 を満たさないため、残りません。
-- 左表を全件残し、条件を満たす注文だけ結合する
SELECT u.name, o.amount
FROM users AS u
LEFT JOIN orders AS o
ON u.id = o.user_id AND o.amount >= 2000;
こちらは鈴木の行も残ります。また、条件に合う注文のないユーザーもNULL行として残ります。
7. JOINで特に気をつけたいこと
-
ONに結合キーを書き忘れると意図しない組み合わせが大量に増える場合があります。 -
SELECT *では同名列が並び、結果の意味が分かりにくくなります。必要な列を指定しましょう。 - 結果の並びは
ORDER BYを明記しなければ保証されません。 - テーブル名には別名(
u、o)を付けると読みやすくなります。
まとめ
INNER JOIN は一致行のみ、LEFT JOIN は左表を残す、RIGHT JOIN は右表を残す、FULL OUTER JOIN は両方を残す。実務では結果が何行になるかを考えて使うのがおすすめです。