SQL中同比与环比增长率计算方法详解
时间:2026-08-17 | 作者:多维游侠 | 阅读:0所谓同比,就是拿当期和去年同一时期对比,比如2024-03对2024-03;环比则是和紧邻的上一期相比,比如2024-03对2024-02。这里有个很容易踩的坑:直接用LAG()往前取一行,往往并不可靠,因为它跟着行序走,不是真正按时间逻辑判断;一旦碰上缺失期、跨年,或者数据粒度压根不是按月,结果就很容易偏掉。更稳妥的处理方式是,先按时间维度完成聚合,再通过JOIN,或者先补齐时间序列后再配合窗口函数来计算,这样才能把“同期”的定义卡准。
什么是同比和环比,为什么不能直接用 LAG() 算增长率
所谓同比,就是和去年同一时期相比(比如 2024-03 vs 2024-03);环比,则是和紧挨着的上一期相比(比如 2024-03 vs 2024-02)。很多人会顺手直接用 LAG(),但这里有个很容易踩的坑:它拿到的只是“相邻行”,不是天然意义上的“上个月”或“去年同月”。月度数据完整连续时,也许碰巧能对上;可一旦出现月份缺失、跨年,或者改成按周、按季度聚合,问题就会立刻暴露出来——LAG(value, 1) 取到的,未必是“上个月”,更不可能自动等于“去年同月”。
真正靠谱的做法是:先按时间维度聚合出每期数值,再用 JOIN 或窗口函数关联对应同期数据。核心在于明确“同期怎么定义”,而不是依赖行序。
用 LEFT JOIN 算同比:按年月分组后自连接
适合大多数业务场景,逻辑清晰、兼容性好(所有 SQL 方言都支持),且能自然处理某月无数据的情况(结果为 NULL)。
- 先用
GROUP BY YEAR(date), MONTH(date)或DATE_FORMAT(date, '%Y-%m')聚合销售额等指标 - 把聚合结果表别名为
t_curr,再LEFT JOIN同一表t_prev,条件是t_prev.year = t_curr.year - 1 AND t_prev.month = t_curr.month - 增长率公式为:
(t_curr.amount - COALESCE(t_prev.amount, 0)) / NULLIF(t_prev.amount, 0)—— 注意用NULLIF防除零,用COALESCE把去年空值转 0 或保持NULL视业务而定
SELECT t_curr.yr_mon, t_curr.amount AS curr_amount, t_prev.amount AS last_year_amount, ROUND((t_curr.amount - COALESCE(t_prev.amount, 0)) / NULLIF(t_prev.amount, 0), 4) AS yoy_rate FROM ( SELECT DATE_FORMAT(order_date, '%Y-%m') AS yr_mon, SUM(amount) AS amount FROM orders GROUP BY DATE_FORMAT(order_date, '%Y-%m') ) t_curr LEFT JOIN ( SELECT DATE_FORMAT(order_date, '%Y-%m') AS yr_mon, SUM(amount) AS amount FROM orders GROUP BY DATE_FORMAT(order_date, '%Y-%m') ) t_prev ON t_prev.yr_mon = DATE_FORMAT(DATE_SUB(STR_TO_DATE(CONCAT(t_curr.yr_mon, '-01'), '%Y-%m-%d'), INTERVAL 1 YEAR), '%Y-%m');
用窗口函数 LAG() + 时间偏移算环比,但必须先补全时间序列
如果原始数据本身缺某些月份(比如 2024-02 没订单),LAG() 会跳过空档,把 2024-03 的“上期”连到 2024-01,导致错误。所以必须先生成完整的时间序列,再左连业务数据。
- 用递归 CTE 或日历表生成连续的年月(如从最小日期到最大日期每月一行)
LEFT JOIN业务聚合结果,确保每期都有行(空则amount = NULL)- 在此基础上用
LAG(amount) OVER (ORDER BY yr_mon)才安全 - 注意:PostgreSQL/MySQL 8.0+/SQL Server 支持标准窗口语法;SQLite 不支持
LAG,需改用子查询
关键点不是函数本身,而是输入数据是否“连续”。没补全时间轴就套 LAG,环比数字看着漂亮,实际是错的。
不同数据库对日期函数的写法差异很实际
写一次 SQL 跑遍 MySQL、PostgreSQL、SQL Server 几乎不可能。最常踩坑的是年月提取和日期偏移:
- MySQL:用
DATE_FORMAT(date, '%Y-%m')和DATE_SUB(date, INTERVAL 1 YEAR) - PostgreSQL:用
TO_CHAR(date, 'YYYY-MM')和date - INTERVAL '1 year' - SQL Server:用
FORMAT(date, 'yyyy-MM')(2012+)或CONVERT(VARCHAR(7), date, 120),偏移用DATEADD(YEAR, -1, date) - BigQuery:用
FORMAT_DATE('%Y-%m', date)和DATE_SUB(date, INTERVAL 1 YEAR)
别指望一个写法通用。上线前务必在目标环境里用真实数据跑一遍,尤其验证跨年 1 月 → 去年 1 月的逻辑是否正确。很多线上问题就出在 2024-01 关联不到 2024-01,因为字符串拼接或截取漏了前导零。
免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。
相关文章
更多-
- 迅捷路由器怎么调信号最强,设置时要注意什么?
- 时间:2026-08-27
-
- vivo浏览器怎么卸不掉?原因和解决方法在这里
- 时间:2026-08-27
-
- OPPO R11s黑屏了,怎么强制恢复出厂设置?
- 时间:2026-08-27
-
- 飞利浦显示器包装盒有生产日期和保修期吗?怎么看?
- 时间:2026-08-27
-
- 联想新平板开机必须联网吗?怎么做?
- 时间:2026-08-27
-
- 平板横竖屏切换设置与问题解决
- 时间:2026-08-27
-
- 移动电源容量怎么测?要准备哪些工具?
- 时间:2026-08-27
-
- 荣耀90 Pro防水吗?防水级别多少?怎么用才安全
- 时间:2026-08-27
精选合集
更多大家都在玩
大家都在看
更多-
- 以下哪种食材被称为“地下苹果” 蚂蚁庄园今日答案9.18
- 时间:2026-09-17
-
- 蚂蚁庄园今天答题答案2026年9月18日
- 时间:2026-09-17
-
- 蚂蚁庄园答题今日答案2026年9月18日
- 时间:2026-09-17
-
- 蚂蚁庄园小课堂2026年9月18日最新题目答案
- 时间:2026-09-17
-
- 小鸡答题今天的答案是什么2026年9月18日
- 时间:2026-09-17
-
- 蚂蚁庄园每日答题答案2026年9月18日
- 时间:2026-09-17
-
- 蔬菜洗完掉色,说明是被染色了,是真的吗 蚂蚁庄园今日答案9月18日
- 时间:2026-09-17
-
- 满襟蜡绘花纹巧染就花纹当绣裳说的是哪种传统技艺 蚂蚁新村今日答案2026.9.17
- 时间:2026-09-17
