用LAG()检测断层最稳:当前id与LAG(id)差值不为1即缺号;补缺需生成完整范围再左连接,避免用ROW_NUMBER()伪替代;注意索引、区间分段生成及空表NULL处理。

用 LAG() 找出序列断层位置
序列号缺失的本质是当前值与前一个值不满足“+1”关系。直接用 LAG() 拿到上一行的 id,再做差值判断最稳:
SELECT id,
LAG(id) OVER (ORDER BY id) AS prev_id,
id - LAG(id) OVER (ORDER BY id) AS gap
FROM orders
ORDER BY id;注意:必须用 ORDER BY id 显式排序,否则窗口函数行为不可靠;首行的 prev_id 是 NULL,对应 gap 也是 NULL,这是正常现象,不用过滤或补零。
生成完整连续序列并左连接补缺
光检测出断层不够,得把缺的号“造出来”。用递归 CTE(PostgreSQL/SQL Server)或 generate_series()(PostgreSQL)最直接:
WITH full_range AS (
SELECT generate_series(
(SELECT MIN(id) FROM orders),
(SELECT MAX(id) FROM orders)
) AS id
)
SELECT fr.id, o.order_data
FROM full_range fr
LEFT JOIN orders o ON fr.id = o.id;MySQL 用户需用数字表或变量模拟序列;如果最大值极大(比如千万级),别硬生成全量,应先用 LEAD() 定位缺口区间,再按段生成。
ROW_NUMBER() 不能替代真实 ID 补全
有人试过用 ROW_NUMBER() OVER (ORDER BY id) 当“新序列”,但这只是重编号,不是补缺:
- 它不会还原原始缺失的
id值(比如原数据有 1,2,4,ROW_NUMBER给出 1,2,3,但 3 并非原系统中该补的位置) - 若存在重复
id或业务要求保留原始语义(如订单号含年份前缀),这种方案完全失效 - 它绕过了“缺失检测”这一步,属于用错场景
性能和边界要注意这些点
实际跑起来容易卡在三处:
- 大表没对
id建索引 →ORDER BY id窗口函数会变全表扫描 - 用
generate_series(MIN, MAX)时,若MAX - MIN达百万级,内存和执行时间飙升 → 改用LEAD()先提取所有缺口区间:id + 1 AS start_gap, LEAD(id) OVER (ORDER BY id) - 1 AS end_gap,再对每个区间生成序列 - 空表时
MIN()/MAX()返回NULL→ 外层要加COALESCE或条件判断,否则 CTE 直接报错

















