直接用 group by 算不出环比和同比,必须先按时间粒度聚合(如 mysql 用 date_format、postgresql 用 date_trunc)再用窗口函数 lag() 计算,需确保时间连续、排序唯一、多维分析加 partition by,并保留完整周期以拉取历史值。

直接用 GROUP BY 算不出环比和同比——它只能汇总,不能跨行取数。必须先聚合出时间序列,再用窗口函数拉取历史值。
先按时间粒度聚合,别跳步
原始订单表是日粒度?那得先归一到月(或周、季),否则 LAG() 会拿“昨天”当“上月”,结果全错。
- MySQL 用
DATE_FORMAT(order_date, '%Y-%m');PostgreSQL/Redshift 用TO_CHAR(order_date, 'YYYY-MM')或DATE_TRUNC('month', order_date) - 别用字符串拼接如
CONCAT(YEAR(order_date), '-', MONTH(order_date))——10月会排在2月前面 - 聚合后检查是否每期都有数据:缺月会导致
LAG(sales, 1)跳过空档,取到上上月
用 LAG() 算环比,重点在排序和分区
环比不是“往前推30天”,而是“逻辑上一期”。LAG(sales, 1) 取的就是排序后紧邻的前一行,所以排序字段必须唯一且连续。
- 写死
ORDER BY stat_month,别依赖GROUP BY的隐式顺序 - 多维度分析(比如按
region和product)时,必须加PARTITION BY region, product,漏一个就跨区混算 - 首期结果天然为
NULL,别当成错误;但计算增长率时得套NULLIF(LAG(sales), 0)防除零
同比不能硬写 LAG(sales, 12)
看似简单,但只要中间缺一个月(比如 2023-02 没数据),LAG(sales, 12) 就会取到 2023-01,而非真正的去年同期。
- 更稳的做法:用双字段排序 + 显式匹配,例如
LAG(sales) OVER (PARTITION BY EXTRACT(MONTH FROM stat_date) ORDER BY EXTRACT(YEAR FROM stat_date)) - 或者退一步,用自连接:
LEFT JOIN t prev ON curr.year = prev.year + 1 AND curr.month = prev.month - 如果坚持用偏移量,务必先补全日历表,确保每月至少有一行(哪怕
sales = 0)
别在聚合前过滤时间范围
比如想看 2024 年数据的同比,如果在 WHERE 里只留 stat_month >= '2024-01',那 2023 年的数据就被删了,LAG() 拉不到去年值。
- 正确做法:聚合时保留完整周期(至少含去年同期),最后再用外层
WHERE过滤展示范围 - 若用 CTE,把聚合和窗口分开写,避免逻辑耦合
- 注意时区:从
created_at提取月份时,统一用AT TIME ZONE 'Asia/Shanghai'等显式声明,别让数据库默认时区干扰
真正麻烦的不是写法,而是数据本身是否对齐:月份是否连续、口径是否一致、NULL 是否被误判为 0。窗口函数只是工具,它不会帮你补数据,也不会纠正业务定义偏差。











