LAG()最可靠检测序列断档,因它直接比较相邻行ID差值是否为1;ROW_NUMBER()不能用于真实断档检测,因其生成序号而非真实ID,对跳号、重复或字符串ID完全无效。

用 LAG() 和 ROW_NUMBER() 检测序列断点最直接
连续序列号缺失的本质是「当前值 ≠ 上一个值 + 1」。靠 MIN()/MAX() 只能知道范围,无法定位具体缺哪几个;而 LAG() 能拿到上一行的值,配合 WHERE 筛出断点,效率高且逻辑清晰。
实操建议:
- 确保目标字段有索引(尤其是大表),否则
LAG()窗口排序开销明显 - 先
ORDER BY serial_no再用LAG(serial_no),顺序错会导致误判 - 注意
NULL处理:首行的LAG()返回NULL,需用WHERE prev IS NOT NULL过滤 - 示例:
SELECT serial_no AS missing_after<br>FROM (<br> SELECT serial_no,<br> LAG(serial_no) OVER (ORDER BY serial_no) AS prev<br> FROM records<br>) t<br>WHERE serial_no != prev + 1 AND prev IS NOT NULL;
用 GENERATE_SERIES()(PostgreSQL)或数字表补全后 LEFT JOIN 找缺失
当需要列出所有缺失值(不止断点位置),而不是只找“跳变处”,生成完整序列再反查更稳妥。PostgreSQL 的 GENERATE_SERIES() 是现成工具;MySQL/SQL Server 需预建数字表或用递归 CTE。
实操建议:
- 生成范围必须覆盖实际最小值和最大值:
GENERATE_SERIES((SELECT MIN(serial_no) FROM records), (SELECT MAX(serial_no) FROM records)) -
LEFT JOIN时,右边表用WHERE r.serial_no IS NULL判定缺失,别漏掉IS NULL条件 - 大范围生成(如百万级)可能内存溢出,优先考虑
LAG()方案 - MySQL 8.0+ 可用递归 CTE 替代:
WITH RECURSIVE nums(n) AS (<br> SELECT MIN(serial_no) FROM records<br> UNION ALL<br> SELECT n+1 FROM nums WHERE n < (SELECT MAX(serial_no) FROM records)<br>)<br>SELECT n AS missing_no FROM nums<br>LEFT JOIN records r ON nums.n = r.serial_no<br>WHERE r.serial_no IS NULL;
GROUP BY + HAVING 只适合检测「是否缺失」,不适合定位
有人尝试用 COUNT(*) != MAX(serial_no) - MIN(serial_no) + 1 判断整体是否连续——这只能回答“有没有缺”,但完全无法告诉你缺了哪些、缺多少个。一旦返回 TRUE,还得换别的方法定位。
容易踩的坑:
- 忽略重复值:如果存在重复
serial_no,COUNT(*)会虚高,导致误判为“不缺” - 忽略空值:
MIN()/MAX()自动跳过NULL,但COUNT(*)不跳,结果错乱 - 无法区分单缺 vs 多缺 vs 重叠缺,信息粒度太粗
- 性能未必更好:仍需全表扫描,且计算无索引加速
字符串型序列号(如 'A001', 'A002')必须先转换再判断
直接对字符串 ORDER BY 或 LAG() 会按字典序排('A100'
实操建议:
- PostgreSQL:用
REGEXP_REPLACE(serial_code, '[^0-9]', '', 'g')::INT - MySQL:用
CAST(REGEXP_SUBSTR(serial_code, '[0-9]+') AS SIGNED)(8.0+) - SQL Server:用
TRY_CAST(REPLACE(REPLACE(serial_code, 'A', ''), 'B', '') AS INT)(需确保前缀固定) - 转换后务必验证是否为
NULL(比如纯字母字段),避免LAG()计算时参与运算
真正难的不是写哪条语句,而是确认序列号的业务定义:它是否允许重复?是否分段(如每月重置)?前缀是否可变?这些都会让“连续”的判定逻辑彻底改变。没理清这点,再准的 SQL 也查不准缺漏。

















