lag函数需显式指定partition by和order by,用date_trunc归一化时间并补全序列防错位,同比用join或补全后lag,环比须先归一化再计算,单层窗口复用避免嵌套性能损耗。

LAG函数怎么写才能正确取到上期数据
直接用 LAG(value) 很可能拿错行——它默认按当前 ORDER BY 排序取上一行,但同比/环比要求的是「按时间顺序的上一个周期」,不是物理上一行。必须显式指定排序字段和偏移量,且排序字段要能唯一确定时序(比如用 date_trunc('month', order_date) 归一化月份)。
常见错误是漏掉 PARTITION BY:如果分析多产品线或多地市,不按业务维度分区会导致跨产品取数。正确写法示例:
LAG(sales_amount, 1) OVER (
PARTITION BY product_id
ORDER BY date_trunc('month', order_date)
)
注意:date_trunc('month', ...) 比直接用 order_date 更安全,避免同月多天数据干扰周期对齐。
同比(YoY)计算中如何处理年份错位问题
同比本质是「当前月 vs 去年同月」,不能只靠 LAG(..., 12) 硬偏移——当数据存在缺失月份时,LAG(sales, 12) 会取到去年上上月甚至更早,结果完全失真。
可靠做法是先生成完整的时间序列(用 generate_series 补全所有月份),再左连接原始数据,最后用 LAG 或直接 JOIN 关联上年同月:
- 补全序列后,
LAG(sales, 12)才真正对应「上年同月」 - 若坚持用
JOIN,需确保关联条件为t1.month = t2.month + INTERVAL '1 year',而非简单减12 - 时区敏感:所有日期操作统一用
AT TIME ZONE 'UTC'避免夏令时导致的月份错乱
环比(MoM)遇到月末/季末日期不一致怎么办
用 LAG(sales, 1) 算环比看似简单,但当数据只有每月25日、而2月没有25日时,2月数据会关联到1月25日(而非1月31日),差值失去业务意义。
解决方案分两层:
- 预处理阶段:用
date_trunc('month', order_date)将所有日期归为当月第一天,再聚合(如SUM(sales) GROUP BY date_trunc('month', order_date)) - 计算阶段:在归一化后的结果集上用
LAG(sum_sales),此时每行严格代表一个自然月 - 若需保留原始粒度(如逐日分析),改用
LEAD/LAG配合ROWS BETWEEN窗口限定最近30天内数据,而非固定偏移
性能陷阱:窗口函数嵌套导致执行计划恶化
有人把同比环比写成三层嵌套:SELECT ..., (cur - LAG(cur)) / LAG(cur) AS mom FROM (SELECT ..., LAG(...) AS cur FROM ...)——这会让PostgreSQL重复扫描数据,且无法利用索引。
更高效的方式是单层窗口完成全部计算:
SELECT month, sales, sales - LAG(sales, 1) OVER w AS mom_diff, ROUND(100.0 * (sales - LAG(sales, 1) OVER w) / NULLIF(LAG(sales, 1) OVER w, 0), 2) AS mom_rate, sales - LAG(sales, 12) OVER w AS yoy_diff FROM monthly_sales WINDOW w AS (PARTITION BY product_id ORDER BY month)
关键点:WINDOW w 定义一次复用,避免重复写 OVER (...);NULLIF 防止除零;小数精度用 ROUND(..., 2) 而非 ::numeric 强转,后者可能触发隐式类型转换开销。
真实场景中,月度分析表务必在 (product_id, month) 上建复合索引,否则 PARTITION BY + ORDER BY 窗口函数会强制排序,数据量大时延迟陡增。










