环比增长率是(本期值−上期值)/上期值,需在同一产品线内按连续时间顺序比较相邻周期;直接lag()易因数据缺失导致跨月或跨产品线错位,必须补全时间序列、严格分区并处理除零与null。

什么是环比增长率,为什么不能直接用 LAG() 就完事?
环比增长率 = (本期值 − 上期值) / 上期值,核心是“同一产品线内按时间顺序比较相邻周期”。很多人一上来就写 LAG(value) OVER (PARTITION BY product_line ORDER BY month),结果发现某些产品线在某个月份缺数据,LAG() 拿到的不是真正“上个月”的值,而是上一条记录(可能跨产品线或跳月)。必须确保:① 时间维度连续(比如补全缺失月份),② 分区严格限定为同一 product_line,③ 排序依据是真实可比的时间字段(如 DATE 类型或标准年月码)。
怎么安全地生成连续时间序列并关联产品线?
如果原始表里只有有销量的月份,得先构造完整的时间轴,再与产品线做交叉连接。MySQL 8.0+ 或 PostgreSQL 可用递归 CTE;SQL Server 常用 master..spt_values 或数字表;但最通用的是用日历表(推荐建一张最小粒度为月的 dim_month 表,含 year_month 字段如 '2024-01')。关键步骤:
- 把原始销售表中的时间字段统一转成标准年月格式,例如
DATE_FORMAT(sale_date, '%Y-%m')(MySQL)或TO_CHAR(sale_date, 'YYYY-MM')(PostgreSQL) -
SELECT DISTINCT product_line FROM sales得到所有产品线 - 用
CROSS JOIN把dim_month和产品线关联,再LEFT JOIN回原始销售表,确保每个产品线每月都有行(空值即 0 销量)
计算环比时如何避免除零和 NULL 传播?
LAG() 返回 NULL 时,直接参与除法会导致整行结果为 NULL;上期值为 0 时会报 division by zero(不同数据库报错方式不同,如 PostgreSQL 报 ERROR: division by zero,MySQL 默认静默转为 NULL 但开启 sql_mode=STRICT_TRANS_TABLES 后也会报错)。必须显式处理:
- 用
COALESCE(LAG(sum_sales), 0)防止 NULL 参与运算,但注意:上期为 0 时仍要避免除零 - 正确写法是:
CASE WHEN LAG(sum_sales) OVER w = 0 THEN NULL ELSE (sum_sales - LAG(sum_sales) OVER w) / NULLIF(LAG(sum_sales) OVER w, 0) END - 窗口定义
w必须写全:PARTITION BY product_line ORDER BY year_month,否则跨产品线错位
示例:完整可跑的 PostgreSQL 片段
WITH monthly_sales AS (
SELECT
product_line,
TO_CHAR(sale_date, 'YYYY-MM') AS year_month,
SUM(amount) AS sum_sales
FROM sales
GROUP BY product_line, TO_CHAR(sale_date, 'YYYY-MM')
),
full_grid AS (
SELECT p.product_line, m.year_month
FROM (SELECT DISTINCT product_line FROM sales) p
CROSS JOIN (SELECT DISTINCT year_month FROM dim_month WHERE year_month >= '2023-01') m
),
filled AS (
SELECT
g.product_line,
g.year_month,
COALESCE(m.sum_sales, 0) AS sum_sales
FROM full_grid g
LEFT JOIN monthly_sales m
ON g.product_line = m.product_line AND g.year_month = m.year_month
)
SELECT
product_line,
year_month,
sum_sales,
ROUND(
CASE
WHEN LAG(sum_sales) OVER (PARTITION BY product_line ORDER BY year_month) = 0
THEN NULL
ELSE (sum_sales - LAG(sum_sales) OVER (PARTITION BY product_line ORDER BY year_month))
/ NULLIF(LAG(sum_sales) OVER (PARTITION BY product_line ORDER BY year_month), 0)
END, 4
) AS mom_growth
FROM filled
ORDER BY product_line, year_month;
真正麻烦的不是公式本身,而是时间对齐——如果源数据里「2024-02」实际是 2 月 15 日才入库,而「2024-03」是 3 月 3 日入库,但业务口径要求按自然月统计,就得在聚合前用 DATE_TRUNC('month', sale_date) 对齐,而不是依赖原始录入时间。











