LAG计算环比必须带唯一可比的ORDER BY,分组需用PARTITION BY,不可用LAG/LEAD替代同比,须补全时间序列并用NULLIF防除零,否则结果失真。

LAG 计算环比必须带 ORDER BY,且排序字段要唯一可比
不写 ORDER BY 会直接报错(PostgreSQL/SQL Server)或返回不可靠结果(MySQL 8.0+ 允许但不推荐)。时间字段不能只用 month 字符串,因为 '2024-01' 和 '2024-10' 在字典序里会错乱;优先用 DATE 或 DATE_TRUNC('month', stat_date) 归一化后再排序。
如果按产品线分组计算,漏掉 PARTITION BY product_id 就会导致 A 产品的 3 月去比 B 产品的 2 月——这不是环比,是错位。
-
LAG(sales) OVER (ORDER BY stat_date):全局序列,适合单指标时间线 -
LAG(sales) OVER (PARTITION BY product_id ORDER BY stat_date):各产品独立算,互不干扰 - 同一查询中所有
LAG()调用必须用完全一致的OVER子句,否则优化器可能重排导致值错配
LEAD 用于同比?别被名字误导,它不适合直接算 YoY
LEAD(sales, 12) 看似能取“12 行后”,但实际取的是物理下第 12 行,不是逻辑上“去年同月”。数据若有缺失(比如 2023-05 没记录),2024-05 的 LEAD(sales, 12) 就会落到 2023-04 上,结果完全失真。
真正靠谱的同比,得靠日期运算对齐时间点,再 JOIN 或 LATERAL 匹配:
- PostgreSQL/BigQuery:
stat_date - INTERVAL '1 year'生成去年同期,再 LEFT JOIN 原表 - MySQL 8.0+:
DATE_SUB(stat_date, INTERVAL 1 YEAR),但需COALESCE(..., '1970-01-01')防闰年 2 月 29 日变 NULL - 别用
LAG(sales, 12)或LEAD(sales, 12)替代——它们只认行序,不认日历
NULLIF 是除法安全底线,不是可选项
环比公式是 (current - prev) / prev,而 LAG() 对首行返回 NULL,对空缺月份也返回 NULL。直接除会导致整列 NULL 或报错。更危险的是用 COALESCE(LAG(), 0) 把 NULL 强制填 0——这会让首月变成 (100 - 0) / 0 = ERROR,或让本该无数据的月份显示虚假的 0% 增长。
正确做法是用 NULLIF(prev_sales, 0) 把除零转成 NULL,再让整个表达式自然失效:
ROUND( (sales - LAG(sales) OVER (ORDER BY stat_date)) * 100.0 / NULLIF(LAG(sales) OVER (ORDER BY stat_date), 0), 2 ) AS mom_pct
-
* 100.0强制浮点运算,避免整数除法截断(如5/2 = 2) - 所有
LAG()出现在分子和分母里,必须写两遍、保持窗口定义一致 - 别省略
NULLIF——线上报表凌晨炸一次,够你改三天
数据有缺失月份?LAG 会静默跳过,结果已错
原始数据只有 2024-01、2024-03 两条记录,LAG() 在 03 行取到的是 01 行的值,算出来是“3 月比 1 月涨了 X%”,业务上完全错误。窗口函数不会主动补空档,它只管物理相邻行。
补全时间序列才是前置动作:
- PostgreSQL:
GENERATE_SERIES(MIN(stat_date), MAX(stat_date), '1 month')+ LEFT JOIN - 通用方案:建一张日期维表(
dim_date),含完整年月日,再 LEFT JOIN 业务表 - 补完再跑
LAG(),否则环比数字看着整齐,实则全是幻觉
最常被忽略的一点:同比环比不是加个函数就完事,而是“先确保时间轴连续,再确保窗口定义精准,最后确保除法不崩”。少走一步,报表就埋雷。

















