首页/数据/SQL 速查/同比计算(跨 12 期)
聚合与窗口函数54 条

同比计算(跨 12 期)

LAG(x, 12) 取去年同月值算同比;前提是数据按月连续、每年 12 期,写死偏移量时先校验数据完整性。

SQL 速查数据更新于 2026-10-13

同比怎么算

同比是把当期指标与去年同期作比较。在按月组织的月度数据里,去年同月就是往前数十二行,因此最直接的写法是用偏移函数取到那一行的值再求增长率。这个写法成立的前提是数据按月连续、每年正好十二期;只要中间缺了月份,偏移十二行拿到的就不是去年同期,算出来的增长率看着正常却是错的。所以同比语句通常要和数据完整性校验一起上线。

基本信息

适用平台MySQL 8.0 / PostgreSQL 12 及以上
偏移量按月组织的数据为十二期,按周或按日要重新计算
前提条件同一对象的时间序列必须连续且无缺期
分母处理用空值判断函数把零除转成 NULL,避免报错或得到无穷值
更稳的写法按「年月减一年」自连接,或先与日历表对齐再计算

典型使用场景

  • 经营月报:营业收入、订单量的同比增长率
  • 单品分析:某个商品逐月营收与去年同月的对比
  • 活跃度趋势:月活用户同比变化,判断业务是否进入衰退
  • 异常识别:同比波动超过阈值时标记出来供人工复核
  • 预算回顾:实际完成情况与去年同期的差距

同比计算示例

同比计算示例
SELECT product_id, month, revenue,
       LAG(revenue, 12) OVER (PARTITION BY product_id ORDER BY month) AS last_year,
       ROUND(100.0 * (revenue
             - LAG(revenue, 12) OVER (PARTITION BY product_id ORDER BY month))
             / NULLIF(LAG(revenue, 12) OVER (PARTITION BY product_id ORDER BY month), 0),
             2) AS yoy_pct                                  -- 同比百分比
FROM product_monthly
ORDER BY product_id, month;

-- 更稳的写法:按「年月减一年」自连接,不依赖行数偏移
SELECT c.product_id, c.month, c.revenue, p.revenue AS last_year
FROM product_monthly c
LEFT JOIN product_monthly p
  ON p.product_id = c.product_id
 AND p.month = c.month - INTERVAL '1 year';

两种写法对比

写法前提风险
偏移固定行数序列连续无缺期缺月时取到错误的行,结果静默失真
按年月自连接时间字段可直接做减一年运算写法更长,但能显式暴露缺失对应期
映射到年月序号列存在可用作序号的时间列需要额外维护序号,跨年时容易算错
与日历表对齐有现成的日历维表需要额外建表,胜在口径统一
把跨年的年月拼成数字再相减很容易出错,用日期类型或时间戳做减一年运算更可靠;如果原始数据里月份是字符串,先转成标准时间字段再计算,不要直接对字符串做算术。
分母为零的情况一定要处理:去年同期为零、本期有值时增长率没有数学意义,用空值判断函数把它变成 NULL,比返回一个巨大的数字更诚实,也更便于下游识别。

易错点

  • 数据缺月时用固定行数偏移取去年同期,拿到的其实是更早的月份,报表数字看着合理却完全错误
  • 跨年时把年月当数字相减,出现不存在的月份,应改用日期类型运算
  • 分母不做零值处理,遇到去年同期为零的记录直接报错或得到无意义的大数字
  • 只在测试库验证:测试数据通常是连续的,缺月与停售这类真实情况到生产才暴露
  • 不同商品的起始月份不同,不加分区直接排序偏移,序列会跨对象串联,导致取错行

数据来源与参考

  • MySQL 8.0 Reference Manual · LAG
  • PostgreSQL Documentation · Window Functions
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌