
为什么直接用 LAG() 算环比会出错?
因为 LAG() 拿到的是上一行的原始值,而环比需要的是「上一期的聚合结果」——比如你按月分组后求销售额总和,再和上个月总和对比。如果没先聚合就直接 LAG(Sales),等于拿单条订单去比,结果完全失真。
常见错误现象:LAG() 返回 NULL 或数值明显不合理;或者结果行数暴增(没 GROUP BY 就用了窗口函数)。
- 必须先
GROUP BY时间维度(如YEAR(Month),MONTH(Month)),再对聚合值开窗 -
LAG()的排序字段要和分组逻辑一致,否则“上一期”可能跨年或跳月 - MySQL 8.0+、PostgreSQL、SQL Server 2012+ 支持,但 SQLite 和旧版 MySQL 不行
怎么写一个安全的月度环比增长率?
核心是两层:外层算增长率,内层用 LAG() 拿上期聚合值。不能把 SUM() 和 LAG() 写在同一级 SELECT,否则 SQL 引擎会报错或语义混乱。
使用场景:月度销售报表、用户增长看板、GMV 分析等需要同比/环比的 BI 查询。
- 先用子查询或 CTE 聚合出每月
SUM(Revenue),带时间字段 - 在外层对这个结果集用
LAG(SUM_Rev) OVER (ORDER BY Year, Month) - 增长率公式固定为:
(Current - LAG_Value) / NULLIF(LAG_Value, 0),NULLIF防除零
SELECT Year, Month, SUM_Rev AS Cur_Month, LAG(SUM_Rev) OVER (ORDER BY Year, Month) AS Prev_Month, ROUND((SUM_Rev - LAG(SUM_Rev) OVER (ORDER BY Year, Month)) / NULLIF(LAG(SUM_Rev) OVER (ORDER BY Year, Month), 0), 4) AS MoM_Rate FROM ( SELECT YEAR(OrderDate) AS Year, MONTH(OrderDate) AS Month, SUM(Amount) AS SUM_Rev FROM orders GROUP BY YEAR(OrderDate), MONTH(OrderDate) ) t;
LAG() 的 offset 和 default 怎么选?
默认 LAG(col) 相当于 LAG(col, 1, NULL),但生产环境别依赖默认值。
参数差异直接影响结果可靠性:
-
offset = 1是标准环比;想算“比上上月涨多少”才设为 2 -
default建议显式写成 0 或 0.0,避免首期返回 NULL 导致整个MoM_Rate列为 NULL - 如果时间不连续(比如缺 2 月数据),
LAG()会跳到 1 月,不是“逻辑上上月”,得先补全日期维度再 LEFT JOIN
性能和兼容性要注意什么?
大表上跑带 LAG() 的聚合查询,容易慢,尤其没索引时。
性能影响点很实在:
- 确保
ORDER BY字段(如Year, Month)有联合索引,否则排序成本高 - PostgreSQL 对窗口函数优化较好;MySQL 在 8.0.22 后才有较稳定的
LAG执行计划 - 如果数据库不支持窗口函数(如老版本 MySQL),只能用自连接或变量模拟,但逻辑更重、易出错
最常被忽略的一点:时间字段类型。用 DATETIME 直接 ORDER BY 可能因秒级精度导致顺序错乱,建议先转成 DATE 或用 YEAR()+MONTH() 拼整数做排序键。










