同期对比增长率是(本期值−同期值)/同期值,窗口函数lag()通过按时间排序后向前偏移指定行数获取同期值,但需确保数据连续且正确分区。

什么是同期对比增长率,窗口函数怎么帮上忙
同期对比增长率 = (本期值 − 同期值) / 同期值,比如 2024年3月销售额相比2023年3月的增长率。关键难点在于:既要拿到当前行的值,又要精准取到“一年前同一月份”的那行数据。传统 JOIN 或子查询容易因缺失日期、多对一匹配出错;而窗口函数配合 LAG() 或 LEAD() 可以按时间顺序稳定拉取历史行,无需显式关联。
核心思路是:先按时间字段(如 order_month)排序,再用 LAG(value, n) 向前跳 n 行取值。但注意——LAG() 是基于**行序号偏移**,不是基于时间差。所以必须确保数据已按时间完整、连续排列(比如每月一行),否则“跳12行”不等于“跳12个月”。
用 LAG() 实现月度同期对比(最常用场景)
适用于标准月粒度报表,且数据中每个自然月有且仅有一条汇总记录(如 2023-01, 2023-02…)。此时按 order_month 排序后,第 i 行的同期就是第 i−12 行。
实操建议:
- 确保
order_month是DATE或规范字符串(如'2024-03'),并用ORDER BY order_month显式排序 - 使用
LAG(sales_amt, 12)获取 12 行前的销售额,不是LAG(sales_amt, 1)—— 后者是上月,不是去年同期 - 加上
COALESCE(..., 0)避免除零或 NULL 导致整列变 NULL - 增长率字段建议用
DECIMAL(5,4)或类似类型,避免浮点精度误差
SELECT
order_month,
sales_amt,
LAG(sales_amt, 12) OVER (ORDER BY order_month) AS last_year_sales,
CASE
WHEN LAG(sales_amt, 12) OVER (ORDER BY order_month) > 0
THEN (sales_amt - LAG(sales_amt, 12) OVER (ORDER BY order_month))
/ LAG(sales_amt, 12) OVER (ORDER BY order_month)
ELSE NULL
END AS yoy_growth
FROM monthly_sales;
当数据不连续时,LAG() 失效怎么办
如果某月无销售(如 2023-02 缺失),那么 LAG(sales_amt, 12) 会跳过空月直接取更早的行,导致“2024-03”的同期变成 2023-01 而非 2023-03。这时不能依赖行偏移,得改用时间计算逻辑。
可行方案是:先用 GENERATE_SERIES(PostgreSQL)或递归 CTE(MySQL 8.0+/SQL Server)补全所有月份,再用 LEFT JOIN 关联原始数据,最后在补全后的结果上用 LAG() —— 此时行序与日历严格对齐,LAG(..., 12) 才真正代表“12个月前”。
另一个轻量级替代:用自连接 + 时间运算,例如:
SELECT cur.order_month, cur.sales_amt, prev.sales_amt AS last_year_sales, (cur.sales_amt - prev.sales_amt) / NULLIF(prev.sales_amt, 0) AS yoy_growth FROM monthly_sales cur LEFT JOIN monthly_sales prev ON prev.order_month = TO_CHAR(cur.order_month::date - INTERVAL '1 year', 'YYYY-MM');
注意:TO_CHAR 和 INTERVAL 语法因数据库而异(MySQL 用 DATE_SUB,SQL Server 用 DATEADD),且该写法无法利用窗口函数的高效排序,大数据量时性能明显弱于 LAG()。
分区场景下必须加 PARTITION BY 吗
要看业务维度。如果只算全公司整体的同比,不需要分区;但如果要分区域、分产品线各自算同比,就必须加 PARTITION BY region, product_type,否则 LAG() 会在整个结果集里跨区乱跳。
常见错误:
- 忘记
PARTITION BY,导致华东的 2024-03 去拉华北的 2023-03 数据 - 分区字段含 NULL,不同数据库对 NULL 分区行为不一致(如 PostgreSQL 把所有 NULL 归为同一组,MySQL 可能跳过),建议提前
COALESCE(region, 'UNKNOWN') - 分区后未在
ORDER BY中包含时间字段,导致窗口内顺序不可控,LAG()结果随机
真正难的不是写对语法,而是确认你的“同期”定义是否和业务对齐:是自然年同期?财年同期?还是滚动12个月?一旦时间基准没对齐,再漂亮的 LAG() 也只会放大错误。











