LAG函数必须配合PARTITION BY和ORDER BY才能正确取上期值,如LAG(sales,1) OVER (PARTITION BY region ORDER BY year_month),否则因缺失排序或分区导致值错乱;需用NULLIF和COALESCE处理除零与NULL,避免计算中断或截断。

LAG函数怎么写才能正确取上期值
直接用 LAG(value) 默认取前1行,但时间序列里“上期”不一定是物理上一行——比如数据有缺失月份、或按年/季度聚合后顺序乱。必须配合 ORDER BY 明确时间维度排序,且推荐用带 PARTITION BY 的写法隔离不同业务线或产品线。
常见错误是漏掉 ORDER BY date,导致 LAG 随机取值;或者没加 PARTITION BY product_id,让A产品的12月和B产品的1月被错误关联。
- 正确写法:
LAG(sales, 1) OVER (PARTITION BY region ORDER BY year_month) -
year_month必须是可排序的格式(如'2023-01'字符串或DATE类型),不能是'Jan-2023'这类无序字符串 - 如果要算同比,
LAG(sales, 12)对月度数据才对应去年同月;环比则用LAG(sales, 1)
同比和环比的分母为零怎么处理
当上期值为0(比如新业务首月上线),直接除会报错或得 NULL / Infinity,数据库行为不一致:PostgreSQL 报错,MySQL 返回 NULL,SQL Server 可能抛异常。必须显式判断。
别用 WHERE last_period != 0 过滤——这会丢掉整条记录,导致同比结果断层。应该在计算列里用条件表达式兜底。
- 安全写法:
CASE WHEN LAG(sales, 1) OVER (...) = 0 THEN NULL ELSE (sales - LAG(sales, 1) OVER (...)) * 1.0 / LAG(sales, 1) OVER (...) END - 更简洁(支持大多数引擎):
NULLIF(LAG(sales, 1) OVER (...), 0)替换分母,再配合COALESCE(..., 0)控制最终显示 - 注意乘
1.0或用CAST避免整数除法截断(如5/10 = 0)
为什么用窗口函数比自连接更可靠
有人用 LEFT JOIN t t2 ON t.year_month = DATE_SUB(t2.year_month, INTERVAL 1 MONTH) 算环比,看似直观,但实际埋雷:一旦某个月份数据完全缺失(如2月没销售记录),自连接就断开,该月环比变成 NULL,而用户可能误以为是数据异常而非自然空缺。
窗口函数基于排序位置取值,只要当前行存在,LAG 就按逻辑偏移找——即使中间缺3个月,LAG(sales, 1) 仍取紧邻的上一个有值的月份(除非指定 ROWS BETWEEN 精确控制)。这对监控类报表尤其关键。
- 自连接依赖完整时间轴补全(需先
GENERATE_SERIES或日期维表),LAG 不需要 - LAG 在大数据量下通常更快:单次扫描 + 排序;自连接是 O(n²) 级别关联
- 但注意:
ORDER BY字段若有重复值(如多笔同秒订单),需加唯一字段(如id)保序,否则LAG行为不确定
不同数据库对LAG的兼容性差异
LAG 是 SQL:2003 标准,主流数据库都支持,但细节有坑:
-
MySQL 8.0+支持,但ORDER BY必须包含确定性字段,否则警告;低版本不支持,只能用变量模拟(极难维护) -
PostgreSQL允许LAG(value, offset, default)第三个参数设默认值(如0),避免NULL传播 -
SQL Server和Oracle同样支持三参数,但default值类型必须和value严格一致(比如不能用'0'填数字列) -
ClickHouse要求ORDER BY在SELECT外显式声明(即不能只在窗口函数里写)
真正容易被忽略的是时区和日期解析——比如把字符串 '202301' 直接 ORDER BY,多数数据库当字符串比('202301' '20231' 就错乱。统一转成 DATE 或固定宽度字符串最稳。










