同比怎么算
同比是把当期指标与去年同期作比较。在按月组织的月度数据里,去年同月就是往前数十二行,因此最直接的写法是用偏移函数取到那一行的值再求增长率。这个写法成立的前提是数据按月连续、每年正好十二期;只要中间缺了月份,偏移十二行拿到的就不是去年同期,算出来的增长率看着正常却是错的。所以同比语句通常要和数据完整性校验一起上线。
基本信息
| 适用平台 | 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,比返回一个巨大的数字更诚实,也更便于下游识别。