LAG和LEAD计算同环比的核心是严格排序、闭合分组、空值兜底;缺一不可,否则结果不可信。

MySQL 8.0 中用 LAG 和 LEAD 算同环比,核心不是“会不会写函数”,而是“排序是否严格、分组是否闭合、空值是否兜底”——漏掉任一环节,结果就不可信。
LAG() 算环比必须加 ORDER BY,且时间字段要能线性排序
直接写 LAG(sales) 不报错,但结果随机。窗口函数不保证物理顺序,只按 OVER (ORDER BY ...) 定义的逻辑顺序取值。
- 错误写法:
LAG(sales) OVER ()—— 没有ORDER BY,MySQL 可能按插入顺序、主键顺序甚至优化器决定的顺序排,环比值完全不可复现 - 安全写法:用
DATE_FORMAT(order_date, '%Y-%m')或YEAR(order_date)*100 + MONTH(order_date)生成可排序的月标识,避免MONTH(order_date)导致 2025-12 和 2026-01 被当成同月 - 若原始数据是日粒度且某月多天,先
GROUP BY DATE_FORMAT(order_date, '%Y-%m')再算环比,否则LAG()可能取到同月内前一天,而非上月首日
同比用 LAG(sales, 12) 的前提是数据按月连续无缺失
LAG(sales, 12) 不是“找去年同月”,而是“取前第12行”。如果 2024-03 缺失,那么 2025-03 的 LAG(sales, 12) 会落到 2024-02,而不是跳过断层自动对齐。
- 验证是否连续:查
COUNT(*)按月分组,看是否每月都有一行;或用ROW_NUMBER() OVER (ORDER BY yyyy_mm)检查序号是否等于行号差 - 更稳的做法:在子查询里先补全月份(用递归 CTE 或日历表),再 LEFT JOIN 补零,最后用
LAG()—— 这比依赖行序可靠 - 别用
LAG(sales) OVER (ORDER BY YEAR(yyyy_mm), MONTH(yyyy_mm))代替LAG(sales, 12),因为ORDER BY只控制顺序,不改变偏移量语义
除零和 NULL 必须显式处理,不能靠默认值掩盖逻辑
环比公式 (cur - prev) / prev 在 prev = 0 或 prev IS NULL 时,整列变 NULL 或报错,且不会只影响当行。
- 用
NULLIF(prev, 0)替代裸除:如(cur - prev) * 100.0 / NULLIF(prev, 0),让分母为 0 时返回NULL而非报错 - 别写
LAG(sales, 1, 0)当默认值来“防 NULL”——如果上月真实值是 0,你却填了 0,默认值反而污染业务含义 - 增长率列建议单独计算 prev 值:用 CTE 或子查询先产出
prev_sales列,再统一参与运算,避免重复四次LAG()表达式,既易读又少一次执行开销 -
* 100.0要带小数点,否则 MySQL 可能做整数除法截断(尤其当sales是INT)
PARTITION BY 漏写会导致跨维度串值,且极难排查
比如分析各产品线月销售额环比,漏掉 PARTITION BY product_line,就会出现“A 产品 2025-02 的上期值 = B 产品 2025-01”,而排序字段 ORDER BY month 完全无法阻止这种混算。
- 只要业务上存在并列维度(地区、品类、门店),就必须
PARTITION BY,不能只靠ORDER BY -
PARTITION BY在ORDER BY之前生效:先切组,再组内排序,最后组内取上一行 —— 这个执行顺序决定了它不可替代 - 测试方法:手动挑两组数据(如华东/华南),分别跑两次单组查询,对比合并在一块跑的结果,差异即为漏
PARTITION BY的证据
真正难的从来不是写出 LAG() 那一行,而是确认时间字段是否真的能线性映射业务周期、分组边界是否和业务口径一致、以及 NULL 和 0 在你当前指标里到底代表什么——这些没法靠函数自动判断,得人盯住。


















