环比增长率是本期值与紧邻上期值之差除以上期值,不能仅用LAG()直接除算,因需同时确保时间排序、分组正确、防除零及类型转换。

什么是环比增长率,为什么不能直接用LAG()除一下就完事?
环比增长率 = (本期值 - 上期值) / 上期值 × 100%,核心是「和紧邻的上一个时间点比」。很多人一上来就写 LAG(sales) 然后除一下,结果报错或得 NULL——因为除零(上期值为 0)或隐式类型转换失败(比如整数除整数截断为 0)。更隐蔽的问题是:没处理分区边界(比如每个产品线要独立计算),也没考虑时间顺序是否真正按月/日排好。
用LAG() + 显式类型转换 + COALESCE 防崩溃
必须确保三件事同时成立:时间列已排序、分组逻辑正确、除法操作数非空且为浮点类型。常见错误是只写 LAG(sales) 却没加 ORDER BY month,导致窗口内顺序乱;或者没把 sales 转成 DECIMAL 就直接除。
-
LAG(sales, 1) OVER (PARTITION BY product_id ORDER BY sale_date)—— 必须带PARTITION BY和ORDER BY,否则跨产品混算 - 除法前强制转类型:
CAST(sales AS DECIMAL(10,2)),避免整数除法丢精度 - 用
COALESCE(prev_sales, 0)不够,要防除零:CASE WHEN prev_sales = 0 THEN NULL ELSE (sales - prev_sales) / prev_sales END
示例片段:
SELECT
product_id,
sale_date,
sales,
LAG(sales) OVER (PARTITION BY product_id ORDER BY sale_date) AS prev_sales,
CASE
WHEN LAG(sales) OVER (PARTITION BY product_id ORDER BY sale_date) = 0
THEN NULL
ELSE (sales - LAG(sales) OVER (PARTITION BY product_id ORDER BY sale_date))
/ CAST(LAG(sales) OVER (PARTITION BY product_id ORDER BY sale_date) AS DECIMAL(10,2))
END AS mom_growth_rate
FROM sales_table;时间维度不规整时,别硬套LAG(),改用自连接或生成序列
如果原始数据缺失某个月(比如 2024-02 没记录),LAG() 会拿 2024-01 当「上期」,但业务上要求必须和 2024-02 比——这就不是窗口函数能解决的了。这时候本质是「找每个日期的前一个有效业务日期」,属于时间对齐问题。
- 先用
GENERATE_SERIES()(PostgreSQL)或递归 CTE(SQL Server/MySQL 8.0+)补全时间轴 - 再 LEFT JOIN 原表,保证每期都有记录(缺失则
sales = 0或NULL) - 最后在补全后的结果上跑
LAG(),才真正符合「自然月环比」定义
跳过这步直接算,得到的所谓“环比”只是数据存在顺序的相邻差,和业务口径大概率对不上。
不同数据库对NULL和除零的默认行为差异很大
PostgreSQL 遇到除零抛 division_by_zero 错误;SQL Server 返回 NULL;MySQL 5.7 默认返回 NULL,但开启 STRICT_TRANS_TABLES 会报错。不显式判断分母,同一段 SQL 在不同环境表现可能完全不同。
- 永远不要依赖数据库默认容错,用
CASE WHEN denominator = 0 THEN NULL ELSE ...显式兜底 - 测试时务必构造分母为 0 的样例(比如首月销量为 0 的产品)
- 如果业务允许“0→非0”算 100% 增长,需额外定义逻辑,不能靠窗口函数自动推断
实际生产中,最常被忽略的是时间排序字段的粒度和去重——比如用 created_at(含秒)却没 DISTINCT ON 或 GROUP BY,导致同一天多条记录让 LAG() 拿到错误的“上期”。

















