缺失日期导致窗口计算结果变短或偏移,因窗口函数不自动补行;需先生成完整日期序列再left join补全,并用lag识别断点构造分组键,最后用last_value或max窗口函数向前填充缺失值。

缺失日期导致窗口计算结果变短或偏移
窗口函数不会自动补行,缺一天就少一行参与计算。比如用 AVG() OVER (ORDER BY sale_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) 算7日均值,若2024-03-05无数据,那2024-03-06的窗口里只有6个有效值(而非“补0后7个”),结果既不连续也不对齐业务预期。
常见现象:折线图出现断点、移动平均线突然跳变、累计值在月初/月末异常归零。
- 原始数据是分钟级或秒级时间戳?先用
date_trunc('day', created_at)(PostgreSQL)或DATE(created_at)(MySQL)聚合成日粒度再排序 - 同一天有多条记录?必须加二级排序,如
ORDER BY sale_date, id,否则窗口内行序不确定 - 想强制“每天都有值”?得先生成完整日期序列,再和业务表
LEFT JOIN
用 generate_series 或递归 CTE 补全日期序列
PostgreSQL 推荐用 generate_series(),SQL Server/MySQL 8.0+ 用递归 CTE——不是为了炫技,而是避免手动枚举或依赖外部维表。
关键陷阱:生成范围必须覆盖业务最小/最大日期,且不能只靠写死字符串。动态取边界才可靠:
- PostgreSQL 示例:
SELECT d::date FROM generate_series((SELECT MIN(sale_date) FROM sales)::date, (SELECT MAX(sale_date) FROM sales)::date, '1 day') - MySQL 8.0+ 示例中,
WHERE d 必须写在递归体里,否则初始值超限直接返回空 - SQL Server 要加
OPTION (MAXRECURSION 366),否则默认100层递归撑不住一年数据
PARTITION BY + LAG() 判断断点后再分组计算
单纯补日期解决不了“逻辑断点”问题。比如系统停机3天、节假日无交易、IoT设备掉线——这些不是数据缺失,而是业务意义上的中断。窗口函数仍会把断点后第一行当作前一段的延续。
正确做法是先识别断点,再用分组隔离:
- 用
LAG(sale_date) OVER (ORDER BY sale_date)取上一行日期 - 算差值:
sale_date - LAG(sale_date) OVER (ORDER BY sale_date)(PostgreSQL),结果 > 1 天即为断点 - 构造分组键:
SUM(CASE WHEN days_gap > 1 THEN 1 ELSE 0 END) OVER (ORDER BY sale_date),同一连续段内该值相同 - 后续所有窗口计算都加
PARTITION BY segment_id,比如SUM(amount) OVER (PARTITION BY segment_id ORDER BY sale_date)
补完日期后,如何填 missing amount 值?
LEFT JOIN 后,缺失日期对应 amount 是 NULL,但业务常要求“沿用上一个有效值”(即 last-value fill),而不是填 0。
COALESCE(amount, 0) 只解决“填零”,不满足向前填充需求。真正要用的是窗口函数:
- PostgreSQL:
LAST_VALUE(amount) IGNORE NULLS OVER (ORDER BY sale_date ROWS UNBOUNDED PRECEDING) - MySQL 8.0+:
COALESCE(amount, LAG(amount) IGNORE NULLS OVER (ORDER BY sale_date))(注意 MySQL 不支持IGNORE NULLS,需嵌套子查询或用变量模拟) - 更通用写法(兼容性高):
MAX(amount) OVER (ORDER BY sale_date ROWS UNBOUNDED PRECEDING),前提是 amount 非负且不会重复下降
别忽略时区和类型一致性:如果 sale_date 是 TIMESTAMP WITH TIME ZONE,所有 LAG、ORDER BY、generate_series 都得统一转成同一时区,否则断点判断和排序都会错乱。











