断号是指本该连续的编号序列中缺失某些数值,如ID序列1、2、3、5、6、8中,4和7即为断号;其检测核心是通过NOT EXISTS子查询验证“当前值+1”是否存在,从而定位断号起始位置。

什么是断号?先看实际现象
断号就是一串本该连续的数字中间缺了某些值,比如用户ID序列是 1,2,3,5,6,8,那么 4、7 就是断号点。用子查询检测的核心思路是:对每个现有编号,检查「它+1」是否存在;如果不存在,且它本身不是最大值,那它后面就断了。
用 NOT EXISTS 找出所有断号起始位置
这是最稳定、兼容性最好的写法,适用于 MySQL、PostgreSQL、SQL Server 等主流数据库。关键在于把当前行当作“前一个数”,再用子查询验证“下一个数”是否存在。
SELECT t1.id AS gap_start FROM numbers t1 WHERE NOT EXISTS ( SELECT 1 FROM numbers t2 WHERE t2.id = t1.id + 1 ) AND t1.id < (SELECT MAX(id) FROM numbers);
-
t1.id是候选的断号起点(即“断在它后面”) -
NOT EXISTS子查询判断t1.id + 1是否缺失 - 必须加
t1.id ,否则最大值本身也会被误判为断号起点 - 注意:该结果只给出断点开头,比如
4表示“4 后面断了”,即缺失5;如需列出所有缺失值,得进一步生成区间
用 LEFT JOIN 替代子查询,性能可能更好
当数据量大、id 有索引时,LEFT JOIN 常比相关子查询更快,因为可避免对每行重复执行子查询。
SELECT t1.id AS gap_start FROM numbers t1 LEFT JOIN numbers t2 ON t2.id = t1.id + 1 WHERE t2.id IS NULL AND t1.id < (SELECT MAX(id) FROM numbers);
- 本质和
NOT EXISTS等价,但执行计划更易优化 - 确保
numbers.id上有索引,否则t2.id = t1.id + 1的关联会变全表扫描 - MySQL 5.7+ 和 PostgreSQL 中两者性能差异通常不大;SQL Server 对
NOT EXISTS优化更成熟,可优先选
扩展:查出完整缺失区间(不止一个数)
上面方法只返回断点起始,但真实场景常需知道“从 A 缺到 B”。这时要结合窗口函数找连续段边界——不过得确认数据库是否支持 LAG()/LEAD()。
WITH ranked AS (
SELECT id,
id - ROW_NUMBER() OVER (ORDER BY id) AS grp
FROM numbers
)
SELECT MIN(id) + 1 AS missing_from,
MAX(id) - 1 AS missing_to
FROM ranked
GROUP BY grp
HAVING COUNT(*) != MAX(id) - MIN(id) + 1
AND MIN(id) + 1 <= MAX(id) - 1;-
id - ROW_NUMBER()是识别连续组的经典技巧:同一连续段内该差值恒定 -
HAVING过滤出那些“组内数量 ≠ 跨度”的组,即存在缺口 - 该写法在 PostgreSQL / SQL Server / MySQL 8.0+ 可用;SQLite 不支持窗口函数,得用自连接模拟,复杂度陡增
断号检测看似简单,但容易忽略边界(如最大值误判)、索引缺失导致慢查、或数据库版本不支持窗口函数。真正在生产环境跑,先 EXPLAIN 看执行计划,再确认目标库的语法支持范围。

















