销售环比增长率是(本期值−上期值)/上期值×100%,必须用lag()按product_id和sale_month有序取上月值计算,不可用avg()/sum(),因其不保留时序;需补全缺失月份防错位,否则结果失真。

什么是销售环比增长率,为什么不能直接用 AVG() 或 SUM() 算
环比增长率 = (本期值 − 上期值) / 上期值 × 100%,它依赖「相邻时间周期的有序比较」,而标准聚合函数如 SUM()、AVG() 只能归约数据,不保留行间顺序和时序关系。强行用 GROUP BY product_id 配合 AVG() 会丢失月份维度,根本无法定位“上月”——必须借助窗口函数构造可比的时间对。
用 LAG() 获取上期销售额,再计算差值与比率
核心思路是:先按产品 + 时间分组聚合出每月销售额,再用 LAG() 向上取上一行(即上月)的值。注意排序必须显式写为 ORDER BY sale_month,否则 LAG() 行为不可控。
SELECT
product_id,
sale_month,
monthly_sales,
LAG(monthly_sales) OVER (
PARTITION BY product_id
ORDER BY sale_month
) AS prev_month_sales,
ROUND(
(monthly_sales - LAG(monthly_sales) OVER (
PARTITION BY product_id ORDER BY sale_month
)) * 100.0 / NULLIF(LAG(monthly_sales) OVER (
PARTITION BY product_id ORDER BY sale_month
), 0),
2
) AS mom_growth_rate
FROM (
SELECT
product_id,
DATE_TRUNC('month', sale_date) AS sale_month,
SUM(sale_amount) AS monthly_sales
FROM sales
GROUP BY product_id, DATE_TRUNC('month', sale_date)
) t;
-
NULLIF(..., 0)防止上月销售额为 0 导致除零错误 - PostgreSQL/Redshift 用
DATE_TRUNC('month', ...);MySQL 用DATE_FORMAT(sale_date, '%Y-%m-01');SQL Server 用DATEFROMPARTS(YEAR(sale_date), MONTH(sale_date), 1) - 第一行(每产品最早月份)的
prev_month_sales和mom_growth_rate自动为NULL,这是正常行为,不是 bug
当数据存在月份缺失时,LAG() 会跳过空档,导致计算错位
比如某产品 2024-01、2024-03 有销售,但 2024-02 为 0 销售且无记录。此时 LAG() 会让 03 月直接对比 01 月,结果变成「隔月环比」,而非真实「环比」。必须补全月份维度:
- 生成连续月份序列(可用递归 CTE 或日历表)
- 用
LEFT JOIN补齐每个产品在各月的销售额(空值填 0) - 再对补全后的结果应用
LAG()
漏掉这步,报表里的「3月环比增长120%」可能实际是「1月→3月翻倍」,业务方会立刻质疑数据可信度。
性能与兼容性提醒:窗口函数在旧版 MySQL(
MySQL 5.7 及更早版本不支持 LAG(),强行运行会报错 ERROR 1305 (42000): FUNCTION xxx.LAG does not exist。替代方案只有自连接或变量模拟,但逻辑复杂、易出错且难以维护:
-- MySQL 5.7 兼容写法(仅作示意,慎用于生产) SELECT t1.product_id, t1.sale_month, t1.monthly_sales, ROUND((t1.monthly_sales - t2.monthly_sales) * 100.0 / NULLIF(t2.monthly_sales, 0), 2) AS mom_growth_rate FROM monthly_agg t1 LEFT JOIN monthly_agg t2 ON t1.product_id = t2.product_id AND t2.sale_month = DATE_SUB(t1.sale_month, INTERVAL 1 MONTH);
这种写法在数据量大时性能明显劣于窗口函数,且 DATE_SUB 对非标准日期格式(如 '202401' 字符串)需额外转换。如果数据库版本无法升级,建议优先推动迁移至 MySQL 8.0+ 或其他支持窗口函数的引擎。
真正卡住的往往不是公式怎么写,而是月份是否连续、空值如何定义、以及不同数据库对 LAG() 的实现差异——这些细节不提前对齐,跑出来的数字看着合理,实际经不起业务追问。











