LAG()和LEAD()用于定位序列断点而非直接找出缺失值,需配合ORDER BY使用;真正枚举缺失值须生成完整范围再反向排除,不同数据库有对应方案,但性能需谨慎评估。

用 LAG() 和 LEAD() 检测相邻断点
窗口函数本身不直接“找出缺失值”,而是帮你定位序列中的空隙位置。最常用的是 LAG() 和 LEAD():它们能拿到当前行前一行或后一行的值,从而判断是否跳变。
比如整数 ID 序列中,若当前 id 比前一个 id 大超过 1,就说明中间缺了数。
常见错误是直接用 LAG(id) 却没加 ORDER BY —— 窗口函数依赖排序,没指定顺序结果完全不可靠。
- 必须搭配
ORDER BY id(或对应排序字段)使用OVER() - 只适用于有序、单调递增/递减的数值列;对乱序或含重复值的数据需先去重+排序
- 示例:找出
id序列中所有“断档起点”SELECT id, LAG(id) OVER (ORDER BY id) AS prev_id<br>FROM numbers<br>WHERE id - LAG(id) OVER (ORDER BY id) > 1;
用 GENERATE_SERIES()(PostgreSQL)或自建数字表补全对比
真正列出“缺失哪些具体值”,得先生成完整范围,再做反向排除。PostgreSQL 有内置 GENERATE_SERIES(),MySQL 和 SQL Server 需手动构造数字序列。
性能影响明显:如果最大值是 100 万,生成百万行再 LEFT JOIN 会很慢,尤其在没索引的临时结果上。
- PostgreSQL 示例(找 1 到 max(id) 中缺失的整数):
SELECT s.n AS missing_id<br>FROM GENERATE_SERIES(1, (SELECT MAX(id) FROM numbers)) AS s(n)<br>LEFT JOIN numbers n ON s.n = n.id<br>WHERE n.id IS NULL;
- MySQL 8.0+ 可用递归 CTE 模拟,但深度限制默认 1000,需调大
cte_max_recursion_depth - SQL Server 常用
master..spt_values或ROW_NUMBER()+ 大表生成数字
处理非连续自然数或带小数的场景
窗口函数检测断点的前提是“相邻应差 1”。如果原始数据是日期、浮点数、或步长为 5 的序列,LAG() 仍可用,但判断条件要改。
容易忽略的是精度问题:浮点比较用 > 而非 =,避免 0.1 + 0.2 != 0.3 类误差干扰断点识别。
- 日期序列:用
LAG(dt) OVER (ORDER BY dt),再配合dt - LAG(dt) > INTERVAL '1 day' - 步长为 5 的序列:判断
val - LAG(val) > 5,而非硬写> 1 - 浮点列:建议转成整数倍数处理(如乘 100 后取整),或用
ABS(val - LAG(val)) > 0.001容错
为什么不能只靠 ROW_NUMBER()?
有人尝试用 ROW_NUMBER() OVER (ORDER BY id) 对比 id 自身来查缺失,这仅在“理想序列从 1 开始且无重复”时成立。一旦最小值不是 1,或存在负数、跳跃起始,就会误报。
例如 id 是 100, 101, 103 —— ROW_NUMBER() 给出 1,2,3,和原值相减得 99,99,100,看似“都对齐”,其实漏了 102。
- 正确做法是:先确定理论最小值和最大值(
MIN(id),MAX(id)),再生成该区间全部值去比对 -
ROW_NUMBER()更适合查“第 N 个缺失”或“排名错位”,而不是枚举缺失项 - 复合主键或含业务逻辑的序列(如订单号前缀+数字)必须拆解后再处理,不能直接套用
实际执行时,先确认数据是否已排序、有无重复、范围是否合理,再选检测方式。生成完整序列那步最容易卡住——别在生产环境直接跑百万级 GENERATE_SERIES,先估算范围再分段查。

















