用LAG()识别日期断层:通过DATEDIFF(DAY, LAG(date_col) OVER (ORDER BY date_col), date_col) > 1定位Gap,配合COALESCE()处理首行NULL;再用递归CTE从Gap起始日+1逐日生成缺失记录。

Gap识别:用LAG()发现日期断层
SQL里没有“自动补全日期”的内置能力,得先定位哪里缺了。核心思路是把当前行日期和上一行日期对比,差值不等于1(或指定单位)就说明有Gap。用LAG()最直接:
注意LAG()默认返回NULL给首行,需用COALESCE()兜底,否则DATEADD()会失败;另外日期单位要统一——比如用DAY还是MONTH,直接影响Gap定义。
- 若原始数据是
DATE类型,用LAG(date_col) OVER (ORDER BY date_col) - 计算断层:
DATEDIFF(DAY, LAG(date_col) OVER (ORDER BY date_col), date_col) > 1 - 避免时区/精度干扰:确保列不含时间部分,必要时用
CAST(date_col AS DATE)
递归CTE生成填充序列:从Gap起点到终点逐日展开 识别出Gap后,不能靠单条INSERT补全,得动态生成缺失的每一条记录。递归CTE是唯一能在标准SQL中做“自增迭代”的方式,但必须严格控制终止条件,否则栈溢出。
关键约束:递归锚点必须是Gap起始日+1(即第一个缺失日),递归体每次+1,终止条件为next_date 。别用,否则多插一天。
- 锚点写法:
SELECT DATEADD(DAY, 1, prev_date) AS next_date FROM gaps WHERE ... - 递归体:
SELECT DATEADD(DAY, 1, next_date) FROM sequence WHERE next_date - SQL Server默认递归深度100,Gap超长需加
OPTION (MAXRECURSION n)
窗口+递归组合:用ROW_NUMBER()对齐原始与填充数据
原始数据和递归生成的填充行属于两类来源,合并后需保持时间顺序、并区分“原始存在”和“人工补充”。这时ROW_NUMBER()比RANK()更稳妥——它不跳号,且可按同一ORDER BY date_col保证全局序。
常见错误是分别对两部分排序再UNION,结果顺序错乱。正确做法是UNION ALL之后再套一层查询做统一排序和编号。
- 填充行建议带标记字段,如
'generated' AS source_type,方便后续过滤 -
ROW_NUMBER() OVER (ORDER BY date_col)必须出现在最外层,不能在CTE内提前计算 - 若需保留原始主键,填充行对应字段填
NULL,避免类型冲突
性能与边界:千万级数据慎用递归CTE 递归CTE本质是循环展开,时间复杂度接近O(n²)。当单个Gap跨度超过万级天数,或总Gap数量超千个,执行会明显变慢,甚至触发内存不足。
生产环境更推荐预生成日期维表(Date Dimension Table),用LEFT JOIN + IS NULL判断缺失,再补数据——这能转成哈希连接,效率高一个数量级。
- 递归CTE适合Gap少、跨度小的场景(如补1个月内的工作日)
- 跨年Gap或业务要求毫秒级精度时,
DATEADD()单位选错会导致整批偏移 - PostgreSQL用户注意:
GENERATE_SERIES()比递归CTE更简洁,但MySQL 8.0+才支持递归,5.7需改用数字辅助表
递归深度、日期单位、合并排序这三点,漏掉任何一个都可能让补全结果错位或截断。










