订阅更新

SQL LAG 和 LEAD 怎么做环比、同比?

面试问 076 约 3 分钟读完 2 次阅读

本文目录
  1. 面试怎么回答
  2. 基础写法
  3. 上一行不等于上一天
  4. 环比公式还要保护分母
  5. 排序必须稳定且粒度唯一
  6. LAG/LEAD 不只是做同比环比

面试怎么回答

LAG 和 LEAD 分别取窗口内当前行之前、之后的记录,关键是正确设置 PARTITION BY 和稳定的 ORDER BY。做环比、同比时不能把上一行直接等同于上一自然周期;日期可能缺失,所以应先补齐日历粒度,或按明确日期偏移自连接。计算增长率还要处理首期 NULL 和分母为 0,并先确认缺失周期按 0 还是不可比处理。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

报表要增加昨日环比和去年同比。看起来只要写 LAG,真正上线后却出现一个问题:某天没有数据时,“上一行”变成了两天前。

先记住一句话:LAG 取当前排序位置之前的行,LEAD 取之后的行;它们认识的是行位置,不天然认识“昨天”“上月”和“去年同期”。

基础写法

SELECT dt,
       revenue,
       LAG(revenue, 1) OVER (ORDER BY dt) AS prev_revenue,
       LEAD(revenue, 1) OVER (ORDER BY dt) AS next_revenue
FROM daily_revenue;

如果有多个业务分区,必须写 PARTITION BY:

LAG(revenue) OVER (
  PARTITION BY product_id
  ORDER BY dt
)

否则一个商品的首日可能取到另一个商品的末日数据。

上一行不等于上一天

数据只有 8 月 1 日、8 月 2 日和 8 月 4 日时,8 月 4 日的 LAG(1) 会返回 8 月 2 日。它回答的是“上一条记录”,不是“昨天”。

时间序列对齐

如果业务定义必须是自然日环比,应先生成完整日期骨架,再把每日指标 LEFT JOIN 进去,明确缺失日填 0 还是保留 NULL,然后再使用 LAG。另一种做法是按 current.dt = previous.dt + 1 day 自连接,直接约束日期关系。

月同比更不能简单写 LAG(value, 12) 后就结束。只有每个主体每个月都恰好一行、月份连续且粒度稳定时,它才等价于去年同期。生产里更稳妥的是先构造标准月份键,再按日期偏移关联去年同月。

环比公式还要保护分母

(revenue - prev_revenue) * 1.0
  / NULLIF(prev_revenue, 0) AS growth_rate

首行没有上一期,结果为 NULL;上一期为 0 时也无法计算正常增长率。是否展示为 0、100% 或“不可比”,应该在指标定义中明确,不要让 SQL 默默决定业务语义。

排序必须稳定且粒度唯一

如果同一天有多行,只按 dt 排序,前后关系可能不稳定。要么先聚合到“一行一个主体一天”,要么补充可确定顺序的字段。否则同一批数据重复执行,LAG 可能得到不同邻行。

还要先确认窗口分区:全站指标按日期排序即可;渠道指标应按渠道分区;用户行为序列则按用户分区并按事件时间和唯一事件号排序。

LAG/LEAD 不只是做同比环比

它们还常用于:计算相邻事件间隔、找状态变化、识别连续登录、比较本次和下次价格、判断会话断点。共同前提都是先定义“谁的序列”和“序列怎么排序”。

刷题进度保存在当前浏览器