self join本质是将同一张表通过不同别名当作两张表进行普通join操作;必须使用两个不同别名,否则报错;查全量需用left join;多层关系需with recursive;性能依赖manager_id等字段索引。

SELF JOIN 本质是把一张表当两张用
SELF JOIN 不是特殊语法,就是普通 JOIN,只是左表和右表指向同一张表,靠别名区分。关键在于:必须给表起两个不同别名,否则数据库不知道你在比谁和谁。
比如员工表 employees 有 id 和 manager_id,要查“谁是谁的直属上级”,就得让这张表既当“员工”又当“经理”:
SELECT e1.name AS employee, e2.name AS manager FROM employees e1 JOIN employees e2 ON e1.manager_id = e2.id;
这里 e1 是员工,e2 是其上级——没别名或别名重复会直接报错 ERROR: table name "employees" specified more than once。
查所有层级(含无上级/无下属)要用 LEFT JOIN
上面的 JOIN 只返回有上级的员工。CEO 的 manager_id 是 NULL,会被漏掉。想包含所有人,必须用 LEFT JOIN:
-
LEFT JOIN保证左表(e1)全量保留,右表匹配不上就填NULL - 如果还想要“谁带哪些下属”,就把角色对调:
FROM employees e1 LEFT JOIN employees e2 ON e1.id = e2.manager_id,这时e1是经理,e2是下属 - 注意:
WHERE e2.id IS NULL能筛出没下属的经理;WHERE e1.manager_id IS NULL筛出顶层领导
递归查完整上下级链路得用 WITH RECURSIVE
SELF JOIN 只能查一层关系(如直属上级)。要查“张三 → 李四 → 王五 → CEO”这种多层汇报链,必须用递归 CTE。不是所有数据库都支持,PostgreSQL、SQL Server、MySQL 8.0+ 可以,SQLite 需开启扩展,旧版 MySQL 不行。
示例(查某员工的所有上级):
WITH RECURSIVE chain AS ( SELECT id, name, manager_id, 1 AS level FROM employees WHERE id = 123 -- 起始员工ID UNION ALL SELECT e.id, e.name, e.manager_id, c.level + 1 FROM employees e INNER JOIN chain c ON e.id = c.manager_id ) SELECT * FROM chain;
容易踩的坑:UNION ALL 不能写成 UNION(性能差且可能截断循环),终止条件靠 manager_id 为 NULL 自然结束,无需手动判断——但得确保数据里没环(比如 A 管 B、B 管 A),否则无限循环。
性能和索引必须跟上
SELF JOIN 或递归查询慢,往往不是写法问题,而是缺索引。重点加在关联字段上:
CREATE INDEX idx_emp_manager_id ON employees(manager_id);- 递归查询中,
WHERE初始条件字段(如id)也要有索引 - 别在
manager_id上存字符串(比如 “CEO”),必须是外键式整数 ID,否则无法走索引 - 千万级表做递归时,先用
LIMIT测试深度,避免查出几百层把内存打爆
真实业务里,组织架构变动频繁,递归结果最好缓存或用路径枚举(如 /1/5/12/)代替实时计算——这点常被忽略,等数据量上来才意识到。











