用 id - row_number() over (order by id) 的差值突变定位缺号断点最轻量可控:它不依赖 generate_series,无需预估范围,天然规避内存爆炸,且能精准标识连续区间边界。

直接看结论:用 ROW_NUMBER() 对 ID 排序后跟原始 ID 做差值,是识别缺号最轻量、最可控的方式——它不依赖 generate_series,不扫描全范围,也不怕空表或超大 ID 区间。
为什么不能只靠 ROW_NUMBER() OVER (ORDER BY id) 看缺号
很多人写完 SELECT id, ROW_NUMBER() OVER (ORDER BY id) AS rn FROM t 就停了,但光有编号没用。关键是要对比“本该有的序号”和“实际 ID”之间的偏移:
-
id - rn相同的行,说明它们属于同一段连续序列(比如 ID 为 100–105,rn 是 1–6,差值恒为 99) - 一旦
id - rn发生跳变(如从 99 变成 102),就说明中间缺了 3 个号 - 如果表里有重复 ID 或 NULL,
ROW_NUMBER()仍会编号,但差值逻辑失效——必须提前WHERE id IS NOT NULL且去重
id - ROW_NUMBER() 差值突变即表示缺号位置
真正要查的是“哪些地方断了”,不是“缺了哪些数”。这个差值法能直接定位断点:
SELECT id, id - ROW_NUMBER() OVER (ORDER BY id) AS delta FROM your_table WHERE id IS NOT NULL ORDER BY id;
结果中只要 delta 值变了,前一行就是上一段连续区间的终点,后一行就是下一段的起点。例如:
- ID: 1,2,3 → delta 都是 0
- ID: 5 → delta 变成 1 → 缺了 4
- ID: 6,7 → delta 还是 1 → 连续
这种写法天然规避了 generate_series(1, 1000000) 的内存爆炸风险,也无需预估上下界。
想列出所有缺失的具体数字?别硬算,用 EXCEPT 更稳
如果你真需要输出 4, 103, 104 这样的具体缺号列表,EXCEPT 比 LEFT JOIN 更简洁、语义更清晰,且 PostgreSQL / SQL Server / MySQL 8.0+ 都支持:
SELECT g.id FROM generate_series( (SELECT MIN(id) FROM your_table), (SELECT MAX(id) FROM your_table) ) AS g(id) EXCEPT SELECT id FROM your_table ORDER BY id;
- 注意:若表为空,
MIN/MAX返回NULL,整个generate_series不执行,结果为空——这是合理行为,不是 bug - 如果业务要求“哪怕表空也要返回 1”,得加
COALESCE(MIN(id), 1),但请先确认这是否符合你的真实需求 -
EXCEPT自动去重,比NOT EXISTS少一层嵌套,执行计划通常更优
容易被忽略的边界情况
缺号检测最常翻车的地方不在主逻辑,而在数据质量本身:
- ID 字段类型是
TEXT或带前缀(如'ORD-1001')?ROW_NUMBER()对比完全失效,必须先提取纯数字再 cast - 有脏数据:ID = 0、负数、超大整数(如 9223372036854775807)?会影响
MIN/MAX和差值计算,建议先WHERE id BETWEEN 1 AND 1000000限定业务有效区间 - 并发写入中查缺号?
generate_series + EXCEPT是快照读,没问题;但若用子查询反复查MIN/MAX再拼范围,两次查询之间数据变动会导致漏判——应把MIN和MAX放进 CTE 一次性取










