【初心者向け】SQLのSELF JOIN(自己結合)とは?使い方・実例・注意点をわかりやすく解説

データベース・SQL

「同じテーブル内でデータを比較したい」
「上司と部下の関係を同じテーブルから取得したい」
──こういったケースでは、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別のテーブルと条件で結合一般的なJOINusers と 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で無限ループを防ぐ
  • パフォーマンス注意:大量データでは適切な絞り込みが必要

使い方のコツ

  • 階層構造のデータ(上司・部下関係)
  • 同一カテゴリ内での比較
  • 類似データの検索
  • 時系列データの前後比較

よく使う場面

  • 組織図の表示
  • 商品の価格比較
  • 顧客の紹介関係
  • 地域別店舗の分析
  • 売上の前年同月比較

コメント

タイトルとURLをコピーしました