相关子查询检测断号的核心逻辑是逐行检查当前编号是否存在前驱(如t1.id-1),通过NOT EXISTS定位断号起点;必须引用外层表字段(如t1.id)以触发“一行一查”,兼容老版本但性能随数据量增长而下降。

什么是相关子查询检测断号的核心逻辑
相关子查询检测断号,本质是让每一行去“查自己前后有没有邻居”。比如检查 id 是否缺失,就得对当前行的 id 做一次 SELECT COUNT(*) FROM t WHERE id = t1.id - 1 这类判断。它不依赖窗口函数或变量,兼容 MySQL 5.7、SQL Server 2008 等老版本,但性能随数据量增长明显变差。
关键点在于:子查询里必须引用外部表的列(如 t1.id),否则就变成普通子查询,起不到“逐行比对”的作用。
用 EXISTS 检测前一个数字是否存在(找断号起点)
要找出所有“本该有前驱却没前驱”的记录——也就是连续序列的开头(断号后第一个数)。这时用 NOT EXISTS 最直接:
SELECT t1.id FROM numbers t1 WHERE NOT EXISTS ( SELECT 1 FROM numbers t2 WHERE t2.id = t1.id - 1 ) AND EXISTS ( SELECT 1 FROM numbers t2 WHERE t2.id = t1.id + 1 );
注意三点:
• 第二个 EXISTS 是为了排除最大值(它也没前驱,但不是断号起点)
• 如果表里有负数或零,t1.id - 1 可能越界,需确认业务范围
• id 字段必须有索引,否则每次子查询都是全表扫描,万级数据就明显卡顿
用 COUNT 配合比较找连续段长度(识别长连续块)
想看哪一段连续数字最长?可以用相关子查询统计“从当前行开始向后能连多少个”:
SELECT t1.id,
(SELECT COUNT(*)
FROM numbers t2
WHERE t2.id >= t1.id
AND NOT EXISTS (
SELECT 1 FROM numbers t3 WHERE t3.id = t2.id + 1
)
) AS seq_len
FROM numbers t1
WHERE NOT EXISTS (
SELECT 1 FROM numbers t4 WHERE t4.id = t1.id - 1
);这段语句实际在干这件事:对每个潜在起点 t1.id,数出“以它开头、向后连续、直到断掉”的长度。
• seq_len = 1 表示孤立数字;seq_len = 5 表示它带头一串 5 个连续数
• 性能很敏感:外层每行触发两次子查询,嵌套三层 SELECT,十万行以上慎用
• 不适用于高并发场景,容易锁表(尤其在 MySQL 的 RR 隔离级别下)
为什么不用相关子查询做“补全建议”
有人想用类似 SELECT t1.id + 1 FROM ... WHERE NOT EXISTS (...) 直接生成缺失值列表,这看似合理,但极易漏判:
• 若缺的是 5,6,7,只查 t1.id + 1 会返回 6,7,8,其中 8 可能并不缺
• 缺失区间两端边界难界定,相关子查询缺乏“区间扫描”能力
• 更可靠的做法是先用 LAG()/LEAD()(如果数据库支持)定位缺口,再用 GENERATE_SERIES 或递归 CTE 补全
相关子查询适合判断“有没有”,不适合构造“有哪些”——这个分寸容易被忽略。

















