环比增长率是本期数与上期数之差除以上期数乘以100%,用于反映指标逐期短期变化;其核心难点在于“上期值”需通过LAG()等窗口函数按时间顺序错位获取,而非直接存在字段,且必须配合ORDER BY确保逻辑顺序,否则结果不可靠。

什么是环比增长率,为什么不能直接用除法
环比增长率 = (本期值 - 上期值) / 上期值 × 100%,核心难点在于「上期值」不是现成字段,而是需要从同一张表中按时间顺序向上找一行。如果用子查询或自连接,性能差、逻辑绕;而 LAG() 是专为这类“错位取值”设计的窗口函数,它能稳定、高效地把上一行(或上N行)的某个字段拎出来,和当前行并列计算。
注意:必须明确排序依据,否则 LAG() 返回结果是不可靠的——它不认“自然插入顺序”,只认 ORDER BY 指定的逻辑顺序。
LAG函数基础写法与常见错误LAG() 最小可用形式是 LAG(column_name),但漏掉 ORDER BY 或分区字段是高频翻车点:
- 错误写法:
SELECT LAG(sales) FROM orders; → 缺少 OVER 子句,语法报错
- 危险写法:
SELECT LAG(sales) OVER () FROM orders; → 空 ORDER BY 导致“上一期”随机,结果不可复现
- 正确骨架:
LAG(sales) OVER (ORDER BY date) 或 LAG(sales) OVER (PARTITION BY region ORDER BY date)
SELECT LAG(sales) FROM orders; → 缺少 OVER 子句,语法报错SELECT LAG(sales) OVER () FROM orders; → 空 ORDER BY 导致“上一期”随机,结果不可复现LAG(sales) OVER (ORDER BY date) 或 LAG(sales) OVER (PARTITION BY region ORDER BY date)
如果你的数据按月聚合,但 date 是日粒度,务必先确保已按月归集(如用 DATE_TRUNC('month', date) 或 TO_CHAR(date, 'YYYY-MM')),否则 LAG() 会拿上一天而非上一月的值。
计算环比增长率的完整SQL结构
关键是在同一 SELECT 中同时拿到本期值、上期值,并处理除零和空值:
SELECT
month,
sales,
LAG(sales) OVER (ORDER BY month) AS prev_sales,
CASE
WHEN LAG(sales) OVER (ORDER BY month) = 0 THEN NULL
ELSE ROUND((sales - LAG(sales) OVER (ORDER BY month)) * 100.0 / LAG(sales) OVER (ORDER BY month), 2)
END AS mom_growth_pct
FROM monthly_sales;
说明:
- 避免重复写四次
LAG():实际项目中建议用 CTE 或子查询先算出prev_sales,再引用,提升可读性与执行效率 -
ROUND(..., 2)防止浮点误差;* 100.0强制转为浮点,避免整数除法截断(如 PostgreSQL/MySQL 中5/2 = 2) - 首行的
prev_sales必为NULL,所以mom_growth_pct也必为NULL,这是正常行为,不是 bug
跨年、跨月、多维度时的注意事项
当业务需要分地区、分产品线看环比,或数据存在跨年断层(比如只有 2023-12 和 2024-02,缺 2024-01),LAG() 仍按排序取“物理上一行”,不会自动跳过缺失月份:
- 要严格按日历月对齐,得先补全时间序列(用
GENERATE_SERIES 或日期维表 LEFT JOIN)
- 多维度场景下,
PARTITION BY 必须包含所有分组字段,例如:OVER (PARTITION BY region, product_type ORDER BY month)
- PostgreSQL 支持
LAG(col, offset, default) 的第三个参数填默认值,但 MySQL 8.0+ 不支持 default,需用 COALESCE(LAG(...), 0) 替代——不过用 0 做分母仍会报错,所以还是推荐用 CASE 显式判断
GENERATE_SERIES 或日期维表 LEFT JOIN)PARTITION BY 必须包含所有分组字段,例如:OVER (PARTITION BY region, product_type ORDER BY month)
LAG(col, offset, default) 的第三个参数填默认值,但 MySQL 8.0+ 不支持 default,需用 COALESCE(LAG(...), 0) 替代——不过用 0 做分母仍会报错,所以还是推荐用 CASE 显式判断真正容易被忽略的不是语法,而是数据完整性:LAG 只负责取值,不负责校验时间是否连续、单位是否统一、空值是否已清洗。上线前一定用真实周期数据跑一遍,重点看第一个非 NULL 的环比值是否符合业务直觉。










