SQLの自己結合(self join)とは、同じテーブルに2つの異なるエイリアスを割り当て、1つのクエリの中で結合する技法です。「社員テーブルから各社員と直属の上司の名前を並べて取得したい」「同じテーブル内で条件を満たす行どうしを横に比較したい」――そんな場面で自己結合が活躍します。特別な新構文はなく、通常のJOINにテーブルエイリアスを付けるだけです。本記事では、従業員テーブルを題材に基本構文・LEFT JOINとの使い分け・行比較の実用例・よくある落とし穴まで、MySQL 8.0の実例で順を追って解説します。
このシナリオで考える
以下の employees テーブルを使います。manager_id 列が同じテーブルの employee_id を参照する自己参照構造で、社員と上司の関係を1テーブルだけで表現しています。こうした「自分自身を外部キーで参照する」テーブル設計は、組織図・カテゴリツリー・コメントの返信スレッドなど、階層データを扱う場面でよく登場します。
CREATE TABLE employees (
employee_id INT PRIMARY KEY,
employee_name VARCHAR(50) NOT NULL,
manager_id INT, -- NULLはトップ(社長)を示す
salary DECIMAL(10,2),
FOREIGN KEY (manager_id) REFERENCES employees(employee_id)
);
INSERT INTO employees VALUES
(1, '鈴木一郎', NULL, 200000), -- 社長
(2, '田中花子', 1, 120000), -- 営業部長
(3, '佐藤健太', 1, 130000), -- 開発部長
(4, '山田次郎', 2, 75000), -- 営業担当
(5, '伊藤美咲', 2, 78000), -- 営業担当
(6, '小林翔', 3, 80000); -- 開発担当
6名のうち鈴木一郎だけが manager_id = NULL(トップ)で、残りは誰かの部下です。このデータを使って以降のクエリを確認していきます。
INNER JOINで社員と上司を取得する
自己結合の核心は「同じテーブルに別々のエイリアスを付ける」ことです。employees を e(社員側)と m(上司側)という2つのインスタンスとして扱い、e.manager_id = m.employee_id で結びつけます。
SELECT
e.employee_id,
e.employee_name AS 社員名,
m.employee_name AS 上司名
FROM employees AS e
INNER JOIN employees AS m
ON e.manager_id = m.employee_id
ORDER BY e.employee_id;
+-------------+-----------+-----------+
| employee_id | 社員名 | 上司名 |
+-------------+-----------+-----------+
| 2 | 田中花子 | 鈴木一郎 |
| 3 | 佐藤健太 | 鈴木一郎 |
| 4 | 山田次郎 | 田中花子 |
| 5 | 伊藤美咲 | 田中花子 |
| 6 | 小林翔 | 佐藤健太 |
+-------------+-----------+-----------+
鈴木一郎(社長)が結果に含まれていないことに気づきましたか。manager_id が NULL のため、ON e.manager_id = m.employee_id の評価が UNKNOWN となり、INNER JOIN の条件を満たさないためです。SQLでは NULL = <任意の値> は常に UNKNOWN で、TRUE にはなりません。これは三値論理と呼ばれるSQLの基本原則です。
LEFT JOINで全員を取得する
社長(ルートノード)も含めた全社員を取得するには、INNER JOIN を LEFT JOIN に切り替えます。LEFT JOIN は結合先に一致する行がなくても、結合先の列を NULL で埋めて行を残してくれます。
SELECT
e.employee_id,
e.employee_name AS 社員名,
COALESCE(m.employee_name, 'なし') AS 上司名
FROM employees AS e
LEFT JOIN employees AS m
ON e.manager_id = m.employee_id
ORDER BY e.employee_id;
+-------------+-----------+-----------+
| employee_id | 社員名 | 上司名 |
+-------------+-----------+-----------+
| 1 | 鈴木一郎 | なし |
| 2 | 田中花子 | 鈴木一郎 |
| 3 | 佐藤健太 | 鈴木一郎 |
| 4 | 山田次郎 | 田中花子 |
| 5 | 伊藤美咲 | 田中花子 |
| 6 | 小林翔 | 佐藤健太 |
+-------------+-----------+-----------+
COALESCE(m.employee_name, 'なし') がポイントです。上司のいない行では m.employee_name が NULL になるため、COALESCE で代替文字列に置き換えています。INNER JOINとLEFT JOINの選択基準は次のとおりです。
| 目的 | 使うべきJOIN |
|---|---|
| 上司が存在する社員だけを取得したい | INNER JOIN |
| 全社員(上司なしのトップも含む)を取得したい | LEFT JOIN |
実用例:同じテーブルの行どうしを比較する
自己結合は「同じテーブル内の2行を条件で突き合わせる」用途にも使えます。代表的な2パターンを見ていきます。
給与が上司を上回る社員を検出する
部下の給与が上司の給与を超えているケースを探すには、次のクエリを使います。INNER JOIN の ON 句で上司と部下をつなぎ、WHERE 句で給与大小を絞り込みます。
SELECT
e.employee_name AS 社員名,
e.salary AS 社員給与,
m.employee_name AS 上司名,
m.salary AS 上司給与
FROM employees AS e
INNER JOIN employees AS m
ON e.manager_id = m.employee_id
WHERE e.salary > m.salary;
給与逆転が存在するときのみ行が返ります。同じ発想で「同じ部署の平均給与と各社員の給与を比較する」「同カテゴリ内の最高評価と各商品の評価を並べる」といった用途にも応用できます。
同じ上司を持つ社員をペアで取り出す
同じ上司の直属部下どうしを組み合わせで取得する例です。同じ条件の行どうしを「ペアリング」するパターンで、チームメンバーの相性分析や重複レコードの検出などに使えます。
SELECT
e1.employee_name AS 社員A,
e2.employee_name AS 社員B,
e1.manager_id AS 共通の上司ID
FROM employees AS e1
INNER JOIN employees AS e2
ON e1.manager_id = e2.manager_id
AND e1.employee_id < e2.employee_id; -- 重複排除・自己ペア防止
+-----------+-----------+----------------+
| 社員A | 社員B | 共通の上司ID |
+-----------+-----------+----------------+
| 田中花子 | 佐藤健太 | 1 |
| 山田次郎 | 伊藤美咲 | 2 |
+-----------+-----------+----------------+
e1.employee_id < e2.employee_id の条件が重要です。この条件がないと「田中-佐藤」と「佐藤-田中」という重複ペアに加え、「田中-田中」という自己ペアまで出現します。
よくある落とし穴と注意点
- エイリアスを付け忘れる:同じテーブルを2回参照する際、エイリアスなしでは列名があいまいになりエラーになります。
FROM employees AS eのように必ず別名を付けてください。 - ON句が抜けてデカルト積が発生する:結合条件を省略または間違えると行数が n² に膨れ上がります。100行のテーブルなら10,000行です。実行前に
EXPLAINで行数の見積もりを確認する習慣が大切です。 - NULL との等値比較:
NULL = NULLは TRUE ではなく UNKNOWN です。ON 句に NULL が混在する列を使うと、意図した行が結合されません。ルートノードの検出にはWHERE manager_id IS NULLを使います。 - パフォーマンス対策:ON 句で使う列(
manager_idなど)にインデックスを張ると、結合のコストを大きく下げられます。CREATE INDEX idx_manager ON employees(manager_id);を忘れずに。 - 多段階の階層には再帰CTEを使う:自己結合は1ホップ(直属の上司)しか辿れません。「社長→部長→課長→担当→…」と多段階を再帰的に取得するには
WITH RECURSIVE(再帰CTE)が必要です。
まとめ
自己結合は同じテーブルに2つのエイリアスを付けて結合する技法で、階層構造の取得や行どうしの比較に活躍します。上司のいないトップ行も含めたいときは LEFT JOIN を選び、COALESCE で NULL を補完しましょう。ON 句の条件もれによるデカルト積と NULL の扱いには特に注意が必要です。多段階の階層を辿る場面では、再帰CTEへの移行も検討してください。
参考リンク
アイキャッチ画像: Photo by Jantine Doornbos on Unsplash
