SELF JOIN能查重复关系,是因为它将一张表视为两个逻辑表,通过非对称ON条件(如t1.a = t2.b AND t1.b = t2.a)显式比对两行间业务上的双向对称关系,如互相关注或循环引用,而GROUP BY无法捕获此类语义重复。

SELF JOIN 为什么能查重复关系
SELF JOIN 的本质是把一张表当成两张表来关联,靠 JOIN 条件筛选出“自己和自己匹配”的行。检测重复关系的关键,不是看某字段是否重复(那是 GROUP BY ... HAVING COUNT(*) > 1 干的事),而是看**两条记录在业务逻辑上构成双向或对称关系**——比如用户 A 关注了用户 B,同时用户 B 也关注了用户 A;或者订单表里,order_id 和 ref_order_id 形成循环引用。
这时候单纯去重或分组会漏掉语义层面的重复,必须用 SELF JOIN 显式比对两行之间的关联字段。
写法要点:ON 条件必须非对称
如果写成 t1.id = t2.id,结果全是自连接,毫无意义;真正要捕获的是“互为对方”的关系。常见模式是:t1.a = t2.b AND t1.b = t2.a,且加上 t1.id 避免镜像重复。
- 必须加
t1.id (或 <code>t1.id != t2.id)防止同一行和自己配对 -
ON条件中两个方向的字段要严格对应业务逻辑,比如关注关系里的followee_id和follower_id - 若表无主键或唯一标识,需用
ROW_NUMBER()或临时加ctid(PostgreSQL)等物理标识去区分行
示例(用户互相关注):
SELECT t1.follower_id, t1.followee_id FROM follows t1 JOIN follows t2 ON t1.follower_id = t2.followee_id AND t1.followee_id = t2.follower_id AND t1.follower_id < t2.follower_id;
容易踩的坑:NULL、大小写、空格干扰匹配
SELF JOIN 对数据质量极其敏感。只要其中一侧行的关联字段是 NULL,整条记录就无法参与匹配;字符串字段若存在首尾空格或大小写不一致,也会导致本该匹配的记录被漏掉。
- 先用
WHERE col IS NOT NULL过滤掉空值再 JOIN,否则NULL = NULL在标准 SQL 中不成立 - 字符串比较前统一用
TRIM(UPPER(t1.name)) = TRIM(UPPER(t2.name)),避免隐形差异 - 数值型 ID 混入字符串型字段时,隐式转换可能失败,显式用
CAST()或CONVERT()对齐类型
性能提醒:没索引时 SELF JOIN 很慢
SELF JOIN 是 O(n²) 复杂度操作,10 万行表可能产生百亿次比较。哪怕只加一个 WHERE 先过滤,也能大幅降低中间结果集。
- 务必在 JOIN 条件涉及的字段上建复合索引,例如
(follower_id, followee_id) - 避免在 JOIN 条件里写函数(如
UPPER(followee_id)),会导致索引失效 - MySQL 8.0+、PostgreSQL 可考虑用
LATERAL或 CTE 提前物化子集,但多数场景下加索引最直接有效
实际执行前用 EXPLAIN 看是否走了索引,而不是凭感觉优化。

















