実務のデータベースでは、1つのテーブルだけで完結することは少なく、複数のテーブルから情報を組み合わせて取得する場面が頻繁にあります。
そんな時に欠かせないのが「JOIN(ジョイン)」です。
この記事では、SQLのJOIN構文を初心者向けに解説し、よく使うJOINの種類や活用例を紹介します。
JOINってなに?

JOINの役割
JOINは、共通するカラムをもとに複数のテーブルを結び付けて1つの結果セットにするSQLの仕組みです。
身近な例で理解しよう
例えば、ECサイトを考えてみましょう。
usersテーブル(顧客情報)
id | name | email
---|----------|------------------
1 | 田中太郎 | tanaka@example.com
2 | 佐藤花子 | sato@example.com
3 | 山田次郎 | yamada@example.com
ordersテーブル(注文情報)
id | user_id | order_date | amount
---|---------|------------|-------
1 | 1 | 2025-06-01 | 2500
2 | 1 | 2025-06-02 | 1800
3 | 2 | 2025-06-03 | 3200
この2つのテーブルから「誰がいつ注文したか」を知りたい時、JOINを使います。
基本的な書き方
SELECT users.name, orders.order_date, orders.amount
FROM users
JOIN orders ON users.id = orders.user_id;
結果のイメージ
name | order_date | amount
---------|------------|-------
田中太郎 | 2025-06-01 | 2500
田中太郎 | 2025-06-02 | 1800
佐藤花子 | 2025-06-03 | 3200
ポイント
- 共通するカラム(user_idとid)で関連付け
- 複数のテーブルの情報を1つの結果で取得
- 「ON」で結合条件を指定する
まずはJOINの役割を理解することがスタートです。次は、最もよく使う「INNER JOIN」の書き方を見てみましょう。
INNER JOIN(内部結合)を覚えよう

INNER JOINの特徴
INNER JOIN
は、両方のテーブルに共通するデータだけを取得します。
最も基本的で使用頻度の高いJOINです。
基本的な書き方
SELECT カラム名
FROM テーブルA
INNER JOIN テーブルB ON テーブルA.共通カラム = テーブルB.共通カラム;
実際の使用例
-- 社員と所属部署の情報を表示
SELECT
employees.name AS 社員名,
departments.name AS 部署名
FROM employees
INNER JOIN departments ON employees.dept_id = departments.id;
-- 注文と顧客の情報を表示
SELECT
users.name AS 顧客名,
orders.order_date AS 注文日,
orders.amount AS 金額
FROM users
INNER JOIN orders ON users.id = orders.user_id;
結果の特徴
employeesテーブル
id | name | dept_id
---|----------|--------
1 | 田中太郎 | 1
2 | 佐藤花子 | 2
3 | 山田次郎 | NULL
departmentsテーブル
id | name
---|-------
1 | 営業部
2 | 開発部
3 | 人事部
INNER JOINの結果
社員名 | 部署名
---------|-------
田中太郎 | 営業部
佐藤花子 | 開発部
山田次郎さんは部署が未設定(NULL)なので、結果に含まれません。
複数テーブルのINNER JOIN
-- 注文、顧客、商品の情報を結合
SELECT
users.name AS 顧客名,
products.name AS 商品名,
order_items.quantity AS 数量,
orders.order_date AS 注文日
FROM orders
INNER JOIN users ON orders.user_id = users.id
INNER JOIN order_items ON orders.id = order_items.order_id
INNER JOIN products ON order_items.product_id = products.id;
ポイント
- 両方のテーブルにデータがある行のみ表示
- 「対応するデータが確実にある」場合に使用
- JOINと書くだけでもINNER JOINの意味
INNER JOINは「対応するデータが両方にある時だけ欲しい」場合に使います。次は、片方にデータがないケースも含める「LEFT JOIN」を見てみましょう。
LEFT JOIN(左外部結合)を理解しよう

LEFT JOINの特徴
LEFT JOIN
は、左側のテーブルの全ての行を取得し、右側に対応するデータがあれば結合、なければNULLになります。
基本的な書き方
SELECT カラム名
FROM テーブルA
LEFT JOIN テーブルB ON テーブルA.共通カラム = テーブルB.共通カラム;
実際の使用例
-- 全ユーザーと、その注文履歴を表示
SELECT
users.name AS 顧客名,
orders.order_date AS 注文日,
orders.amount AS 金額
FROM users
LEFT JOIN orders ON users.id = orders.user_id
ORDER BY users.name;
結果の比較
usersテーブル
id | name
---|--------
1 | 田中太郎
2 | 佐藤花子
3 | 山田次郎
ordersテーブル
id | user_id | order_date | amount
---|---------|------------|-------
1 | 1 | 2025-06-01 | 2500
2 | 2 | 2025-06-02 | 1800
LEFT JOINの結果
顧客名 | 注文日 | 金額
---------|------------|------
田中太郎 | 2025-06-01 | 2500
佐藤花子 | 2025-06-02 | 1800
山田次郎 | NULL | NULL
山田次郎さんは注文履歴がないため、注文関連の情報がNULLになりますが、行は表示されます。
実用的な活用例
-- 注文のない顧客を特定
SELECT
users.name AS 顧客名,
users.email AS メールアドレス
FROM users
LEFT JOIN orders ON users.id = orders.user_id
WHERE orders.user_id IS NULL;
-- 部署に所属していない社員を確認
SELECT
employees.name AS 社員名
FROM employees
LEFT JOIN departments ON employees.dept_id = departments.id
WHERE departments.id IS NULL;
-- 全顧客の注文回数を集計(注文のない顧客も含む)
SELECT
users.name AS 顧客名,
COUNT(orders.id) AS 注文回数
FROM users
LEFT JOIN orders ON users.id = orders.user_id
GROUP BY users.id, users.name
ORDER BY 注文回数 DESC;
ポイント
- 左側のテーブルの全行が結果に含まれる
- 対応するデータがない場合はNULLで表示
- 「全件表示+あれば関連情報も」の場合に使用
「全ユーザー一覧と、その注文があれば表示」といったケースに便利です。次は、逆の動きをする「RIGHT JOIN」です。
RIGHT JOIN(右外部結合)を知ろう

RIGHT JOINの特徴
RIGHT JOIN
は、右側のテーブルを基準にして、左側の一致するデータを結合します。
LEFT JOINの逆バージョンです。
基本的な書き方
SELECT カラム名
FROM テーブルA
RIGHT JOIN テーブルB ON テーブルA.共通カラム = テーブルB.共通カラム;
実際の使用例
-- 全部署と、所属している社員を表示
SELECT
departments.name AS 部署名,
employees.name AS 社員名
FROM employees
RIGHT JOIN departments ON employees.dept_id = departments.id
ORDER BY departments.name;
結果のイメージ
employeesテーブル
id | name | dept_id
---|----------|--------
1 | 田中太郎 | 1
2 | 佐藤花子 | 2
departmentsテーブル
id | name
---|-------
1 | 営業部
2 | 開発部
3 | 人事部
RIGHT JOINの結果
部署名 | 社員名
-------|--------
営業部 | 田中太郎
開発部 | 佐藤花子
人事部 | NULL
人事部には社員がいないため、社員名がNULLになります。
LEFT JOINとの書き換え
RIGHT JOINは、LEFT JOINでテーブルの順序を入れ替えることで同じ結果が得られます。
-- RIGHT JOINの書き方
SELECT departments.name, employees.name
FROM employees
RIGHT JOIN departments ON employees.dept_id = departments.id;
-- 同じ結果をLEFT JOINで書く
SELECT departments.name, employees.name
FROM departments
LEFT JOIN employees ON departments.id = employees.dept_id;
注意点
- MySQLなど一部のデータベースでのみサポート
- PostgreSQLやSQLiteでは使用できない
- LEFT JOINで代替可能なため、使用頻度は低い
ポイント
- 右側のテーブルの全行が結果に含まれる
- LEFT JOINの方が一般的で推奨される
- どちらを使うかは好みやチーム内の規約による
LEFT JOINと同じく、NULLを含む結合が必要な時に使います。次は、両テーブルの全行を対象とするFULL JOINです。
FULL OUTER JOIN(全外部結合)を理解しよう

FULL OUTER JOINの特徴
FULL OUTER JOIN
は、左右両方のテーブルに存在する全ての行を取得し、どちらかにない場合はNULLで埋めます。
基本的な書き方
SELECT カラム名
FROM テーブルA
FULL OUTER JOIN テーブルB ON テーブルA.共通カラム = テーブルB.共通カラム;
実際の使用例(PostgreSQL)
-- 全社員と全部署の情報を表示
SELECT
employees.name AS 社員名,
departments.name AS 部署名
FROM employees
FULL OUTER JOIN departments ON employees.dept_id = departments.id;
結果のイメージ
employeesテーブル
id | name | dept_id
---|----------|--------
1 | 田中太郎 | 1
2 | 佐藤花子 | 2
3 | 山田次郎 | NULL
departmentsテーブル
id | name
---|-------
1 | 営業部
2 | 開発部
3 | 人事部
FULL OUTER JOINの結果
社員名 | 部署名
---------|-------
田中太郎 | 営業部
佐藤花子 | 開発部
山田次郎 | NULL
NULL | 人事部
MySQLでのFULL OUTER JOIN代替方法
MySQLはFULL OUTER JOINをサポートしていないため、UNION を使って代替します。
-- MySQLでの代替方法
SELECT employees.name, departments.name
FROM employees
LEFT JOIN departments ON employees.dept_id = departments.id
UNION
SELECT employees.name, departments.name
FROM employees
RIGHT JOIN departments ON employees.dept_id = departments.id;
実用的な活用例
-- 在籍状況と給与支払い状況の突合
SELECT
COALESCE(staff.name, payroll.name) AS 名前,
staff.department AS 部署,
payroll.salary AS 給与
FROM staff
FULL OUTER JOIN payroll ON staff.id = payroll.staff_id;
対応状況
- PostgreSQL:サポートあり
- SQL Server:サポートあり
- Oracle:サポートあり
- MySQL:サポートなし(UNIONで代替)
- SQLite:サポートなし
ポイント
- 両方のテーブルの全データを保持
- データベースによってサポート状況が異なる
- 完全な突合や差分チェックに使用
全データを保持したまま結合したい時に使います。最後に、JOINの使い分けと注意点を整理します。
JOINの使い分けと注意点

JOINの種類別比較
種類 | 対象行 | NULL表示の有無 | 使用場面 |
---|---|---|---|
INNER JOIN | 両方に一致する行のみ | なし | 確実に関連がある場合 |
LEFT JOIN | 左テーブル全行+一致行 | 右にない場合はNULL | 全件表示+関連情報 |
RIGHT JOIN | 右テーブル全行+一致行 | 左にない場合はNULL | LEFT JOINの逆 |
FULL OUTER JOIN | 両テーブルの全行 | 一方にない場合はNULL | 完全な突合 |
よくある間違いと注意点
1. 結合条件を忘れる
-- ❌ 間違い:結合条件がない(カーティジアン積になる)
SELECT users.name, orders.order_date
FROM users, orders;
-- ✅ 正しい:ON句で結合条件を指定
SELECT users.name, orders.order_date
FROM users
INNER JOIN orders ON users.id = orders.user_id;
2. テーブル名の省略
-- ❌ あいまい:どちらのテーブルのnameか不明
SELECT name FROM users JOIN orders ON users.id = orders.user_id;
-- ✅ 明確:テーブル名を明記
SELECT users.name FROM users JOIN orders ON users.id = orders.user_id;
-- ✅ エイリアスを使用
SELECT u.name FROM users u JOIN orders o ON u.id = o.user_id;
3. NULLの扱いを理解していない
-- LEFT JOINでNULLをチェック
SELECT u.name
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.user_id IS NULL; -- 注文のない顧客
パフォーマンスを向上させるコツ
インデックスの活用
-- 結合に使用する列にインデックスを作成
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_employees_dept_id ON employees(dept_id);
適切なJOINの選択
-- 必要なデータのみ取得
SELECT u.name, o.order_date
FROM users u
INNER JOIN orders o ON u.id = o.user_id
WHERE o.order_date >= '2025-06-01' -- 条件を先に絞る
LIMIT 100; -- 必要な件数のみ
複数JOINの実践例
-- 注文詳細レポート
SELECT
u.name AS 顧客名,
o.order_date AS 注文日,
p.name AS 商品名,
oi.quantity AS 数量,
oi.price AS 単価,
(oi.quantity * oi.price) AS 小計
FROM users u
INNER JOIN orders o ON u.id = o.user_id
INNER JOIN order_items oi ON o.id = oi.order_id
INNER JOIN products p ON oi.product_id = p.id
WHERE o.order_date >= '2025-06-01'
ORDER BY o.order_date DESC, u.name;
ポイント
- 目的に応じてJOINの種類を選択
- 結合条件は必ず指定する
- テーブル名やエイリアスで明確性を保つ
- インデックスでパフォーマンスを向上
JOINは強力な機能ですが、正しい理解と構文で使いこなすことが重要です。
まとめ
SQLのJOINは、複数テーブルのデータを組み合わせて使いたい時に不可欠な構文です。
覚えるべきポイント
- INNER JOIN:両方にデータがある行のみ取得
- LEFT JOIN:左側の全行+対応する右側データ
- RIGHT JOIN:右側の全行+対応する左側データ(使用頻度は低い)
- FULL OUTER JOIN:両方の全行(MySQLは非対応)
実務での使い分け
INNER JOINを使う場面
- 注文と顧客の関連情報
- 社員と部署の確実な組み合わせ
- 商品と在庫の関連データ
LEFT JOINを使う場面
- 全顧客リスト(注文履歴の有無に関係なく)
- 全社員リスト(部署配属の有無に関係なく)
- マスタデータと実績データの組み合わせ
よく使われるパターン
-- 売上ランキング(顧客別)
SELECT
u.name,
COUNT(o.id) AS 注文回数,
SUM(o.amount) AS 合計金額
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name
ORDER BY 合計金額 DESC;
-- 在庫切れ商品の確認
SELECT p.name
FROM products p
LEFT JOIN inventory i ON p.id = i.product_id
WHERE i.stock IS NULL OR i.stock = 0;
コメント