SQL の結合(JOIN)は複数のテーブルを組み合わせてデータを取得する際に欠かせない機能です。INNER JOIN・LEFT JOIN・RIGHT JOIN の3種類はよく似ていますが、返す行の範囲がそれぞれ異なります。この記事では実際のテーブルとデータを使って3種類の挙動を比較し、どの場面でどれを選ぶかの判断基準を整理します。基本的な SELECT 文が書ける方を対象に、MySQL 8.0 を想定して解説します。
要点
- INNER JOIN:両テーブルに一致する行だけを返す
- LEFT JOIN:左テーブルの全行 + 右テーブルの一致行(なければ NULL)
- RIGHT JOIN:右テーブルの全行 + 左テーブルの一致行(LEFT JOIN のテーブル順を入れ替えたものと同義)
このシナリオで考える
今回は「会員テーブル(members)」と「注文テーブル(orders)」の2テーブルを使います。
CREATE TABLE members (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL
);
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
member_id INT,
amount INT NOT NULL,
order_date DATE NOT NULL,
FOREIGN KEY (member_id) REFERENCES members(id)
);
INSERT INTO members VALUES (1, '田中'), (2, '鈴木'), (3, '佐藤');
INSERT INTO orders VALUES
(1, 1, 5000, '2026-05-01'),
(2, 1, 3000, '2026-05-10'),
(3, 2, 8000, '2026-05-12');
会員は3人ですが、注文レコードは田中さんと鈴木さんの計3件だけです。佐藤さん(id=3)は一度も注文していない状態を前提に進めます。
INNER JOINの基本
INNER JOIN は「両テーブルに一致する行だけ」を返します。ON 句に書いた結合条件を満たさない行は結果から除外されます。
SELECT m.name, o.amount, o.order_date
FROM members m
INNER JOIN orders o ON m.id = o.member_id;
| name | amount | order_date |
|---|---|---|
| 田中 | 5000 | 2026-05-01 |
| 田中 | 3000 | 2026-05-10 |
| 鈴木 | 8000 | 2026-05-12 |
注文のない佐藤さんは orders テーブルに行がないため、結果に現れません。INNER JOIN は「両方に存在するデータだけ見たい」場面に適しています。注文者の詳細情報を取得する、売上明細を確認するといった用途が典型例です。
JOIN とだけ書いた場合も INNER JOIN と同じ挙動になります。明示的に INNER を書くと意図が明確になり、可読性が上がります。
LEFT JOINで「片側だけ」を取る
LEFT JOIN は「左テーブル(FROM 直後)の全行」を必ず返し、右テーブルに一致する行があれば結合します。一致しない場合は右テーブルの列が NULL になります。
SELECT m.name, o.amount, o.order_date
FROM members m
LEFT JOIN orders o ON m.id = o.member_id;
| name | amount | order_date |
|---|---|---|
| 田中 | 5000 | 2026-05-01 |
| 田中 | 3000 | 2026-05-10 |
| 鈴木 | 8000 | 2026-05-12 |
| 佐藤 | NULL | NULL |
佐藤さんの行が amount=NULL、order_date=NULL で追加されました。LEFT JOIN の典型的な使いどころは「注文をしていない会員を一覧で出したい」などの「A はあるが B はない」行を抽出する場面です。
未マッチの行だけを取り出すには、WHERE 句で右テーブルの列が NULL かどうかを確認します。
-- 一度も注文していない会員を抽出
SELECT m.name
FROM members m
LEFT JOIN orders o ON m.id = o.member_id
WHERE o.id IS NULL;
| name |
|---|
| 佐藤 |
「LEFT JOIN + WHERE IS NULL」のパターンは頻出です。押さえておくと実務で役立ちます。
RIGHT JOINと代替の考え方
RIGHT JOIN は LEFT JOIN の反転で、右テーブルの全行を返します。左テーブルに一致しない場合は左側の列が NULL になります。
-- RIGHT JOIN で書く場合
SELECT m.name, o.amount
FROM members m
RIGHT JOIN orders o ON m.id = o.member_id;
これは次の LEFT JOIN と同じ結果になります。
-- テーブル順を入れ替えて LEFT JOIN で書き直す
SELECT m.name, o.amount
FROM orders o
LEFT JOIN members m ON o.member_id = m.id;
FROM のテーブル順を入れ替えるだけで RIGHT JOIN は LEFT JOIN に書き換えられます。JOIN の向きを統一すると読む人が混乱しにくくなるため、常に LEFT JOIN で統一しているチームも多いです。
3種類の違いを表でまとめます。
| 種類 | 返す行 | 典型的な用途 |
|---|---|---|
| INNER JOIN | 両テーブルに一致する行 | 関連データの取得 |
| LEFT JOIN | 左テーブルの全行 + 右の一致行 | 欠けているデータの確認・全件リスト |
| RIGHT JOIN | 右テーブルの全行 + 左の一致行 | LEFT JOIN で代替可 |
よくある落とし穴
WHERE 条件で LEFT JOIN が INNER JOIN になる
LEFT JOIN を使っているのに意図せず INNER JOIN と同じ結果になるケースがあります。右テーブルの列を WHERE 句で絞った場合です。
-- NG: WHERE で絞ると佐藤が除外される
SELECT m.name, o.amount
FROM members m
LEFT JOIN orders o ON m.id = o.member_id
WHERE o.order_date >= '2026-05-10';
佐藤さんの o.order_date は NULL のため、WHERE 条件を満たさず結果から消えます。全会員を残したまま期間を絞りたいなら、条件を ON 句へ移します。
-- OK: ON 句で絞ると佐藤(NULL 行)が残る
SELECT m.name, o.amount
FROM members m
LEFT JOIN orders o ON m.id = o.member_id
AND o.order_date >= '2026-05-10';
| name | amount |
|---|---|
| 田中 | 3000 |
| 鈴木 | 8000 |
| 佐藤 | NULL |
ON 句は「結合するかどうかの条件」、WHERE 句は「結合後の行を絞る条件」です。この区別を押さえるだけで LEFT JOIN 絡みのバグの大半は防げます。
NULL の比較には IS NULL を使う
NULL を比較するとき、= NULL は常に FALSE になります。NULL との比較は必ず IS NULL / IS NOT NULL を使ってください。
-- NG: NULL は = で比較できない(常に FALSE)
WHERE o.id = NULL
-- OK
WHERE o.id IS NULL
まとめ
INNER JOIN・LEFT JOIN・RIGHT JOIN の使い分けは「必要な行の範囲」で決まります。両テーブルに存在するデータだけ見たいなら INNER JOIN、片側の全行を保持したいなら LEFT JOIN を選んでください。LEFT JOIN を使う際は ON 条件と WHERE 条件の違いを意識することで、想定外の行の欠落を防げます。
参考リンク
アイキャッチ画像: Photo by Caspar Camille Rubin on Unsplash
