LAG()仅取紧邻上一行值,遇连续NULL仍返回NULL;要找最近非空值需用COUNT分组+FIRST_VALUE广播;补时间轴缺口须先生成完整序列再填充。

直接用 LAG() 配合 COALESCE() 只能填“上一行的值”,对连续多个 NULL 无效——它不会跳过中间的空行去找更早的非空值。
LAG(col) OVER (ORDER BY ts) 返回 NULL 时怎么办
这是最常见误判点:LAG(amount) OVER (ORDER BY ts) 取的是物理上紧邻的上一行,如果那行也是 NULL,结果还是 NULL。比如时间序列中 2024-01-02、2024-01-03 都是 NULL,那么 2024-01-03 的 LAG() 就是 2024-01-02 的 NULL,不是 2024-01-01 的真实值。
- 别指望
COALESCE(LAG(amount), LAG(amount, 2), LAG(amount, 3))能兜住所有空——深度不可控,且性能随偏移量线性下降 - 真正要“向前找最近非空值”,得靠分组:用
COUNT(amount) OVER (ORDER BY ts ROWS UNBOUNDED PRECEDING)构造grp,再用FIRST_VALUE(amount) OVER (PARTITION BY grp ORDER BY ts ROWS UNBOUNDED PRECEDING) -
ROWS UNBOUNDED PRECEDING必须显式写出,否则默认RANGE在时间戳重复时会聚合错误行
COALESCE(LAG(), default) 的类型陷阱
COALESCE() 要求所有参数类型兼容,但 LAG() 的返回类型和原始列一致,而你硬写的默认值可能不匹配。
- PostgreSQL 中
COALESCE(LAG(amount), 0)看似可行,但如果amount是DECIMAL(10,2),整数0会被隐式转为NUMERIC,某些场景下触发精度警告 - 稳妥写法是显式转换:
COALESCE(LAG(amount) OVER (ORDER BY ts), 0::DECIMAL(10,2))(PostgreSQL)或CAST(0 AS DECIMAL(10,2))(通用) - MySQL 不支持
LAG(col, offset, default)的第三个参数,别写LAG(amount, 1, 0)——这在 MySQL 会报语法错
为什么不能只靠 LAG/LEAD 补全断层时间点
原始数据缺了某天整条记录(比如根本没有 2024-05-01 这一行),LAG() 和 LEAD() 根本看不到它——它们只在已有行之间跳转,不会凭空生成新行或填充时间轴缺口。
- 补时间轴必须两步走:先用
GENERATE_SERIES(PostgreSQL)、递归 CTE(SQL Server / MySQL 8.0+)或日历表LEFT JOIN把空档补成NULL行 - 再对这些新产生的
NULL行做填充,此时才能用窗口函数 - 把
RIGHT JOIN当“自动补全”是典型误解——它只是保留右表,填出来的NULL还得靠COALESCE或分组广播来处理
真正难的不是写对一个 LAG(),而是想清楚:这个 NULL 是“有记录但值为空”,还是“压根没这条时间记录”。前者可窗口填充,后者必须先插行——漏掉这层区分,所有后续逻辑都会偏航。

















