「同じテーブル内でデータを比較したい」
「上司と部下の関係を同じテーブルから取得したい」
──こういったケースでは、SELF JOIN(自己結合)が非常に有効です。
SELF JOINは少し難しそうに感じますが、同じテーブルを2回参照して結びつけるだけです。
この記事では、SELF JOINの仕組み、基本構文、実務で使える具体例、さらには注意点まで初心者にもわかりやすく解説します。
SELF JOIN(自己結合)って何?

SELF JOINの定義
SELF JOIN(自己結合)とは、同じテーブルを2回参照して、関連のある行同士を結合するSQLのテクニックです。
まるで同じテーブルを「コピーして2つに分けて、それらをJOINする」ようなイメージです。
なぜSELF JOINを使うの?
こんな場面で役立ちます
- 上下関係(上司と部下など)を表示したい
- 同じカテゴリ内での比較をしたい
- 類似データを見つけたい
- 同じテーブル内で条件付きの比較をしたい
基本的な書き方
SELECT A.列名, B.列名
FROM テーブル名 A
JOIN テーブル名 B ON A.条件列 = B.条件列;
重要なポイント
- 同じテーブルを2回書く
- 必ず別名(A、B など)をつける
- 結合条件でどの行同士を関連付けるかを指定
基本的な仕組みの例
employeesテーブル
+----+----------+-----------+------------+
| id | name | department| manager_id |
+----+----------+-----------+------------+
| 1 | 田中部長 | 営業部 | NULL |
| 2 | 佐藤主任 | 営業部 | 1 |
| 3 | 山田社員 | 営業部 | 2 |
| 4 | 鈴木部長 | 開発部 | NULL |
| 5 | 高橋社員 | 開発部 | 4 |
+----+----------+-----------+------------+
このテーブルから「社員とその上司」の組み合わせを取得したい場合、SELF JOINを使います。
SELF JOINの実例解説
例1:上司と部下の関係を表示
SELECT
emp.name AS 部下名,
mgr.name AS 上司名
FROM employees AS emp
JOIN employees AS mgr ON emp.manager_id = mgr.id;
実行結果
+----------+----------+
| 部下名 | 上司名 |
+----------+----------+
| 佐藤主任 | 田中部長 |
| 山田社員 | 佐藤主任 |
| 高橋社員 | 鈴木部長 |
+----------+----------+
解説
employees
テーブルをemp
(部下)とmgr
(上司)の2つの別名で参照emp.manager_id = mgr.id
で部下の上司IDと上司の社員IDを結合- 結果として、各社員とその上司の組み合わせが取得できる
例2:同じ部署内の他メンバーを表示
SELECT
A.name AS 社員A,
B.name AS 同部署社員B
FROM employees AS A
JOIN employees AS B ON A.department = B.department
WHERE A.id <> B.id; -- 自分自身は除く
実行結果
+----------+--------------+
| 社員A | 同部署社員B |
+----------+--------------+
| 田中部長 | 佐藤主任 |
| 田中部長 | 山田社員 |
| 佐藤主任 | 田中部長 |
| 佐藤主任 | 山田社員 |
| 山田社員 | 田中部長 |
| 山田社員 | 佐藤主任 |
| 鈴木部長 | 高橋社員 |
| 高橋社員 | 鈴木部長 |
+----------+--------------+
解説
- 同じ部署(
department
)の人同士を結合 WHERE A.id <> B.id
で自分自身との組み合わせを除外- 結果として、同じ部署の他のメンバーとの全組み合わせが取得できる
例3:給与比較での活用
-- 自分より給与が高い同僚を表示
SELECT
A.name AS 社員名,
A.salary AS 自分の給与,
B.name AS より高い給与の同僚,
B.salary AS 同僚の給与
FROM employees AS A
JOIN employees AS B ON A.department = B.department
WHERE A.salary < B.salary;
例4:経験年数での比較
-- 同じ部署で入社が早い先輩を表示
SELECT
junior.name AS 後輩,
senior.name AS 先輩,
junior.hire_date AS 後輩入社日,
senior.hire_date AS 先輩入社日
FROM employees AS junior
JOIN employees AS senior ON junior.department = senior.department
WHERE junior.hire_date > senior.hire_date;
より実践的な使用例

顧客の紹介関係を表示
-- 顧客テーブルでの紹介関係
SELECT
customer.name AS 顧客名,
referrer.name AS 紹介者名
FROM customers AS customer
LEFT JOIN customers AS referrer ON customer.referred_by = referrer.id;
商品の価格比較
-- 同カテゴリで価格が近い商品を表示
SELECT
A.product_name AS 商品A,
A.price AS 価格A,
B.product_name AS 商品B,
B.price AS 価格B,
ABS(A.price - B.price) AS 価格差
FROM products AS A
JOIN products AS B ON A.category = B.category
WHERE A.id <> B.id
AND ABS(A.price - B.price) < 1000
ORDER BY A.category, 価格差;
地域別店舗の距離計算
-- 同じ地域内の店舗間距離(概算)
SELECT
A.store_name AS 店舗A,
B.store_name AS 店舗B,
SQRT(
POW(A.latitude - B.latitude, 2) +
POW(A.longitude - B.longitude, 2)
) AS 距離
FROM stores AS A
JOIN stores AS B ON A.region = B.region
WHERE A.id <> B.id
ORDER BY 距離;
売上データの前年比較
-- 月別売上の前年同月比較
SELECT
current_year.month AS 月,
current_year.sales AS 今年売上,
last_year.sales AS 昨年売上,
current_year.sales - last_year.sales AS 売上差,
ROUND(
(current_year.sales - last_year.sales) / last_year.sales * 100, 1
) AS 成長率
FROM monthly_sales AS current_year
JOIN monthly_sales AS last_year
ON current_year.month = last_year.month
AND current_year.year = last_year.year + 1;
SELF JOINの種類と使い分け
INNER SELF JOIN
-- 上司が存在する社員のみ表示
SELECT emp.name AS 社員, mgr.name AS 上司
FROM employees AS emp
INNER JOIN employees AS mgr ON emp.manager_id = mgr.id;
LEFT SELF JOIN
-- 上司がいない社員も含めて全員表示
SELECT
emp.name AS 社員,
COALESCE(mgr.name, '上司なし') AS 上司
FROM employees AS emp
LEFT JOIN employees AS mgr ON emp.manager_id = mgr.id;
RIGHT SELF JOIN
-- 部下がいない社員も含めて表示
SELECT
COALESCE(emp.name, '部下なし') AS 部下,
mgr.name AS 上司
FROM employees AS emp
RIGHT JOIN employees AS mgr ON emp.manager_id = mgr.id;
注意点とベストプラクティス
必ず別名(エイリアス)を使う
間違った例
-- これはエラーになる
SELECT employees.name, employees.name
FROM employees
JOIN employees ON employees.manager_id = employees.id;
正しい例
SELECT emp.name, mgr.name
FROM employees AS emp
JOIN employees AS mgr ON emp.manager_id = mgr.id;
無限ループを避ける
問題のある例
-- 自分自身との比較も含まれてしまう
SELECT A.name, B.name
FROM employees AS A
JOIN employees AS B ON A.department = B.department;
改善された例
-- 自分自身を除外
SELECT A.name, B.name
FROM employees AS A
JOIN employees AS B ON A.department = B.department
WHERE A.id <> B.id;
パフォーマンスに注意
問題のある例
-- 大量のデータで実行すると非常に遅い
SELECT A.name, B.name
FROM large_table AS A
JOIN large_table AS B ON A.category = B.category;
改善された例
-- 適切な条件で絞り込む
SELECT A.name, B.name
FROM large_table AS A
JOIN large_table AS B ON A.category = B.category
WHERE A.created_date >= '2025-01-01'
AND B.created_date >= '2025-01-01'
AND A.id <> B.id
LIMIT 1000;
インデックスの活用
-- 結合に使用する列にインデックスを作成
CREATE INDEX idx_manager_id ON employees(manager_id);
CREATE INDEX idx_department ON employees(department);
-- より効率的な実行が可能
SELECT emp.name, mgr.name
FROM employees AS emp
JOIN employees AS mgr ON emp.manager_id = mgr.id;
SELF JOINと他のJOINの違い

JOINの種類 | 使用目的 | 特徴 | 使用例 |
---|---|---|---|
INNER JOIN | 別のテーブルと条件で結合 | 一般的なJOIN | users と orders の結合 |
LEFT JOIN | 主テーブルのデータを全て表示 | NULLを含むデータも取得 | 注文していない顧客も表示 |
SELF JOIN | 同じテーブル同士を結合 | 自己比較や階層構造に最適 | 上司と部下、同部署メンバー |
通常のJOINとの使い分け
通常のJOIN
-- 異なるテーブル間の関係
SELECT u.name, o.order_date
FROM users u
JOIN orders o ON u.id = o.user_id;
SELF JOIN
-- 同じテーブル内の関係
SELECT emp.name, mgr.name
FROM employees emp
JOIN employees mgr ON emp.manager_id = mgr.id;
高度なSELF JOINテクニック
階層の深さを調べる
-- 各社員の階層レベルを計算
WITH RECURSIVE employee_hierarchy AS (
-- 最上位(上司がいない人)
SELECT id, name, manager_id, 0 AS level
FROM employees
WHERE manager_id IS NULL
UNION ALL
-- 部下たち
SELECT e.id, e.name, e.manager_id, eh.level + 1
FROM employees e
JOIN employee_hierarchy eh ON e.manager_id = eh.id
)
SELECT name, level AS 階層レベル
FROM employee_hierarchy
ORDER BY level, name;
複数段階の比較
-- 3世代の関係(祖父・父・子)
SELECT
grandparent.name AS 祖父,
parent.name AS 父,
child.name AS 子
FROM employees AS grandparent
JOIN employees AS parent ON parent.manager_id = grandparent.id
JOIN employees AS child ON child.manager_id = parent.id;
同一カテゴリ内での順位付け
-- 部署内での給与順位
SELECT
A.name AS 社員名,
A.salary AS 給与,
COUNT(B.id) + 1 AS 部署内順位
FROM employees AS A
LEFT JOIN employees AS B
ON A.department = B.department
AND A.salary < B.salary
GROUP BY A.id, A.name, A.salary
ORDER BY A.department, 部署内順位;
よくあるエラーと対処法
別名の指定忘れ
エラー例
Column 'name' in field list is ambiguous
対処法
-- 必ず別名を使って明確にする
SELECT emp.name, mgr.name -- OK
FROM employees AS emp
JOIN employees AS mgr ON emp.manager_id = mgr.id;
結合条件の間違い
間違った例
-- 意図しない結果になる
SELECT A.name, B.name
FROM employees AS A
JOIN employees AS B ON A.id = B.id; -- 同じ人同士の結合
正しい例
SELECT A.name, B.name
FROM employees AS A
JOIN employees AS B ON A.manager_id = B.id; -- 上司と部下の結合
パフォーマンス問題
問題のあるクエリ
-- デカルト積になってしまう
SELECT A.name, B.name
FROM large_table AS A, large_table AS B;
改善されたクエリ
-- 適切な結合条件を指定
SELECT A.name, B.name
FROM large_table AS A
JOIN large_table AS B ON A.category_id = B.category_id
WHERE A.id <> B.id;
まとめ
SQLのSELF JOINは、一見難しそうに思えますが、実は「同じテーブルを2回参照して結合する」だけのシンプルな構造です。
特に上下関係や同一カテゴリ内の比較など、実務で活躍する場面が多いテクニックです。
重要なポイント
- 同じテーブルを2回参照:別名を必ずつける
- 適切な結合条件:どの行同士を関連付けるかを明確に
- 自分自身の除外:
WHERE A.id <> B.id
で無限ループを防ぐ - パフォーマンス注意:大量データでは適切な絞り込みが必要
使い方のコツ
- 階層構造のデータ(上司・部下関係)
- 同一カテゴリ内での比較
- 類似データの検索
- 時系列データの前後比較
よく使う場面
- 組織図の表示
- 商品の価格比較
- 顧客の紹介関係
- 地域別店舗の分析
- 売上の前年同月比較
コメント