LAG算同比差一年是因为默认取上一行而非跨12个时间单位;需确保时间字段为DATE类型、按自然月对齐、补全缺失月份,且ORDER BY与分区字段一致。

为什么直接用 LAG 算同比老是差一年?
因为 LAG 默认按当前排序取上一行,而同比需要跨「12个时间单位」(比如12个月),不是简单上一行。如果时间字段是 YYYY-MM 字符串或未对齐的日期,LAG(col, 1) 会拿上个月数据,而非去年同期——这会导致 2024-02 和 2023-02 错配成 2024-02 与 2024-01。
实操要点:
- 先确保时间字段是真实
DATE类型,不是字符串;否则ORDER BY dt可能按字典序排(如 '2023-10' > '2023-9') - 用
LAG(value, 12)前,必须按自然月粒度对齐:补全缺失月份(用GENERATE_SERIES或左连接日历表),否则第13行未必是去年同月 - PostgreSQL 支持
LAG(value, 12) OVER (ORDER BY dt),但 MySQL 8.0+ 才支持,5.7 不行
环比计算中 LAG 的偏移量到底填几?
填 1 就够了——只要时间序列已按天/月严格连续排序。但「连续」不等于「存在」:如果某天没数据,LAG(sales, 1) 会跳过空缺,直接取再上一个有值的日期,导致环比变成「上一个有销售日」而非「昨天」。
解决方法:
- 先生成完整时间序列(如用
SELECT generate_series('2024-01-01'::date, '2024-12-31'::date, '1 day')),再和业务表LEFT JOIN - 对金额类字段,
LAG前用COALESCE(sales, 0)填零,避免 NULL 干扰计算 - 别在
WHERE里过滤时间后再开窗——会先删行再算LAG,导致偏移错位;应把过滤条件放进子查询或 CTE
同比 SQL 写出来结果全为 NULL?常见三个漏点
不是函数不会用,而是数据链路断在了前面。最常踩的坑:
-
ORDER BY字段和分区字段不一致:比如按region分区却按dt排序,LAG只在每个 region 内找上12行,跨 region 错乱 - 时间字段含时分秒但没截断:2023-01-01 10:30:00 和 2024-01-01 09:15:00 排序正常,但
LAG取的是上一条记录,未必是去年同日 - 没处理时区:数据库时区和业务时区不一致时,
dt::date可能错一天,建议统一转成dt AT TIME ZONE 'Asia/Shanghai'::date
MySQL 5.7 没 LAG 怎么硬刚同比?
用自连接 + 日期运算模拟:给每行算出「去年同期时间点」,再关联自身。
示例(月度销售表 sales_month,字段 ym 是 '2024-01' 格式字符串):
SELECT cur.ym, cur.amount, last.amount AS amount_last_year FROM sales_month cur LEFT JOIN sales_month last ON last.ym = DATE_FORMAT(DATE_SUB(STR_TO_DATE(CONCAT(cur.ym, '-01'), '%Y-%m-%d'), INTERVAL 1 YEAR), '%Y-%m');
注意点:
-
STR_TO_DATE和DATE_FORMAT成对出现,避免字符串比较 - 如果
ym是整数型(如 202401),改用last.ym = cur.ym - 100更快,但需确保无跨世纪场景(如 200001 → 199901) - 这种写法无法利用索引加速,大数据量时务必给
ym加函数索引(MySQL 8.0+)或冗余生成列
真正麻烦的从来不是函数怎么写,而是时间对齐这件事本身——它要求你同时懂业务周期、数据采集逻辑和数据库时区配置。少对齐一次,同比就偏一季。










