用lag()/lead()算同比环比最轻量,需按时间严格排序;常见错误是缺order by、排序不唯一或在group by后误用窗口函数;非标周期须先构造可排序业务标识。

怎么用 LAG() 和 LEAD() 算同比/环比
直接用窗口函数是最轻量、最可控的方式,不需要自连接或子查询。核心是按时间排序后,把上期值“拉下来”和当前行对齐。
常见错误是没写 ORDER BY 或排序字段不唯一,导致 LAG() 返回结果错乱;还有人用 GROUP BY 后再套窗口函数,结果报错 —— 窗口函数必须在聚合之后(或不用聚合)执行。
-
LAG(value, 1) OVER (PARTITION BY product_id ORDER BY dt):取同一产品中前1天的值(日粒度环比) -
LAG(value, 12) OVER (PARTITION BY product_id ORDER BY year_month):月粒度同比,前提是year_month是连续整数(如 202301、202302…),否则得转成序列号 - 如果时间字段有空缺(比如某天没数据),
LAG()会跳过空行取真实存在的上一行,不是“逻辑上上月”,这点容易误判
遇到非标准周期(如财年、双周)怎么处理
窗口函数本身不理解业务周期,只认排序顺序。所以“上一财年同期”这种需求,不能硬凑 LAG(value, n),得先构造可排序的业务周期标识。
例如财年从每年4月开始,想比“2023财年Q2 vs 2022财年Q2”,就得先把日期映射成 fiscal_year_quarter 字段(如 '2023-Q2' → 20232),再按它 ORDER BY。
- 别在
ORDER BY里写表达式如YEAR(dt) * 10 + QUARTER(dt),部分数据库不支持;先用SELECT子句算好别名,再在OVER里引用 - 双周场景建议生成一个
biweek_id整数列(如FLOOR(DATEDIFF(dt, '2020-01-01') / 14)),比字符串拼接更稳 - MySQL 8.0+ 支持
WINDOW命名复用,多个LAG()可共用同一个OVER定义,减少重复写
ROW_NUMBER() 和 RANK() 在同环比里有什么用
它们不直接算差值,但能解决“取每个分组最新N期”这类前置问题。比如要查每个产品最近3个月的环比,就得先筛出每组 top 3 的记录,再算差。
误用 RANK() 是高频坑:当存在相同日期的多条记录(如不同渠道汇总到同一天),RANK() 会并列编号并跳号,导致 WHERE rn 漏掉数据;<code>ROW_NUMBER() 更可靠,但需明确 ORDER BY 的二级排序字段(如加 id 防随机)。
-
ROW_NUMBER() OVER (PARTITION BY product_id ORDER BY dt DESC, id DESC):确保稳定取最新3条 - 别在
WHERE里直接用窗口函数,必须嵌套一层子查询或 CTE,否则语法报错 - Oracle 和 PostgreSQL 支持
QUALIFY(BigQuery 也支持),可以简化WHERE套娃,但 MySQL 不行
性能差、查不动?先看这三处
同环比查询慢,90% 出在窗口函数没走索引、分区裁剪失效或数据膨胀。不是函数本身慢,是执行计划歪了。
-
PARTITION BY字段必须有索引,且和ORDER BY字段合建联合索引(如(product_id, dt)),单建dt索引无效 - 如果表按
dt分区,但WHERE条件没限定分区键(比如只写WHERE product_id = 'A'),整个表扫描不可避免 - 避免在
OVER子句里用函数转换字段,如ORDER BY YEAR(dt),会导致索引失效;提前在 WHERE 或 JOIN 中预处理
复杂点在于:时间维度一旦叠加多层业务逻辑(财年+季度+滚动周期),窗口定义就容易和物理数据分布脱节。这时候宁可拆成两步——先物化中间周期表,再跑窗口——也别硬扛一个超长 SQL。










