【SQL】SQL同环比计算的多种实现方式

【SQL】SQL中同环比的多种计算方式

  • 一、引言
  • 二、同环比的核心概念
  • 三、实现与案例
    • [方法一:LEAD / LAG 窗口函数(最常用)](#方法一:LEAD / LAG 窗口函数(最常用))
      • [1. 环比(按月)](#1. 环比(按月))
      • [2. 同比(按年)](#2. 同比(按年))
    • [方法二:自关联(Self JOIN)](#方法二:自关联(Self JOIN))
      • [1. 环比自关联](#1. 环比自关联)
      • [2. 同比自关联](#2. 同比自关联)
    • [方法三:日期偏移法(DATE_SUB / DATE_ADD)](#方法三:日期偏移法(DATE_SUB / DATE_ADD))
      • [1. 环比(DATE_SUB)](#1. 环比(DATE_SUB))
      • [2. 同比(DATE_SUB 减1年)](#2. 同比(DATE_SUB 减1年))
  • 四、总结对比

一、引言

数据分析里绕不开的一个话题------同环比计算

汇报的时候,管理者经常会问:"这个月比上个月增长了多少?"、"今年相比去年同期表现如何?" 这些问题背后的答案,都离不开同环比。

  • 同比:和去年同一时期比
  • 环比:和上一个周期比。

今天讲讲SQL的几种主流写法。


二、同环比的核心概念

基本概念梳理:

概念 说明 举例
同比(YoY) 和去年同一时期比 今年4月 vs 去年4月
环比(MoM) 和上一个周期比 今年4月 vs 今年3月
时间偏移函数 用 OFFSET / LAG 等取历史数据 数据分析必备
窗口函数 在分组内做聚合与偏移 复杂场景下更灵活

常见踩坑避雷点:

  • 月份/日期不连续时,偏移后数据对不上
  • 年度同期计算要注意闰年和月份天数差异
  • 聚合粒度不同会导致结果偏差

三、实现与案例

下面以一张销售数据表 sales 为例,演示几种主流写法:

sql 复制代码
-- 先建表 
CREATE TABLE sales (
    mt        DATE,         -- 销售月
    amount    DECIMAL(10,2) -- 销售额
);

测试数据如下:

mt amount
2024-01-01 1000
2024-02-01 1200
2024-03-01 1100
2024-04-01 1500
2025-01-01 1300
2025-02-01 1400
2025-03-01 1350

方法一:LEAD / LAG 窗口函数(最常用)

原理: 使用 LAG(col, n)/LEAD(col,n) 往前/往后取 n 行的数据,直接在同一行里完成对比。这个函数也是面试中,经常会考的,一定要熟练应用。
优点:

  • ✅ 一行出结果,逻辑清晰
  • ✅ 性能好,窗口函数内部优化强
  • ✅ 兼容 MySQL 8.0+ 和 Hive

缺点:

  • ❌ 需要数据按时间连续,否则会跳空
  • ❌ 不支持跨年/跨月批量偏移

1. 环比(按月)

sql 复制代码
-- 环比:和上月比
SELECT
    mt,
    amount,
    LAG(amount, 1) OVER (ORDER BY mt) AS last_month_amount,  -- 上月销售额
    amount - LAG(amount, 1) OVER (ORDER BY mt) AS moom_diff, -- 环比增长额
    ROUND((amount - LAG(amount, 1) OVER (ORDER BY mt)) /
          LAG(amount, 1) OVER (ORDER BY mt) * 100, 2) AS moom_rate  -- 环比增长率%
FROM sales
ORDER BY mt;

结果:

mt amount last_month_amount moom_diff moom_rate
2024-02-01 1200 1000 200 20.00
2024-03-01 1100 1200 -100 -8.33
... ... ... ... ...

2. 同比(按年)

sql 复制代码
-- 同比:去年同一时期
SELECT
    mt,
    amount,
    LAG(amount, 12) OVER (ORDER BY mt) AS last_year_amount,  -- 去年同月
    ROUND((amount - LAG(amount, 12) OVER (ORDER BY mt)) /
          LAG(amount, 12) OVER (ORDER BY mt) * 100, 2) AS yoy_rate  -- 同比增长率%
FROM sales
ORDER BY mt;

核心思路: 数据是月粒度,偏移 12 行就是去年同月。


方法二:自关联(Self JOIN)

原理: 把表和自身作JOIN,按时间条件关联历史数据。
优点:

  • ✅ 适合复杂 JOIN 场景(多表联动)

缺点:

  • ❌ 数据量翻倍,碰上大数据量长周期的,效率非常低
  • ❌ 需要注意 NULL 情况(首月/首年无历史数据)
  • ❌ 连续月份数据不能断层,否则漏关联

1. 环比自关联

sql 复制代码
-- 环比自关联
SELECT
    a.mt,
    a.amount AS cur_amount,
    b.amount AS last_month_amount,
    a.amount - b.amount AS moom_diff,
    ROUND((a.amount - b.amount) / b.amount * 100, 2) AS moom_rate
FROM sales a
LEFT JOIN sales b
    ON DATE_SUB(a.mt, INTERVAL 1 MONTH) = b.mt  -- 上月 = 本月减1个月
ORDER BY a.mt;

2. 同比自关联

sql 复制代码
-- 同比自关联
SELECT
    a.mt,
    a.amount AS cur_amount,
    b.amount AS last_year_amount,
    ROUND((a.amount - b.amount) / b.amount * 100, 2) AS yoy_rate
FROM sales a
LEFT JOIN sales b
    ON DATE_SUB(a.mt, INTERVAL 1 YEAR) = b.mt  -- 去年同一月
ORDER BY a.mt;

方法三:日期偏移法(DATE_SUB / DATE_ADD)

原理: 直接构造目标日期,然后去原表查对应数据。适合临时查询和调试,原理其实和法2差不多

优点:

  • ✅ 代码最简单,容易读懂
  • ✅ 适合临时查询、一次性脚本

缺点:

  • ❌ 大数据量下 JOIN 性能差
  • ❌ 无索引时全表扫描,速度慢
  • ❌ 日期不连续时结果会丢行

1. 环比(DATE_SUB)

sql 复制代码
-- 环比:查上月
SELECT
    a.mt,
    a.amount,
    b.amount AS last_month_amount,
    ROUND((a.amount - b.amount) / b.amount * 100, 2) AS moom_rate
FROM sales a
LEFT JOIN sales b
    ON b.mt = DATE_SUB(a.mt, INTERVAL 1 MONTH)
ORDER BY a.mt;

2. 同比(DATE_SUB 减1年)

sql 复制代码
-- 同比:查去年同月
SELECT
    a.mt,
    a.amount,
    b.amount AS last_year_amount,
    ROUND((a.amount - b.amount) / b.amount * 100, 2) AS yoy_rate
FROM sales a
LEFT JOIN sales b
    ON b.mt = DATE_SUB(a.mt, INTERVAL 1 YEAR)
ORDER BY a.mt;

四、总结对比

方法 原理 适用场景 难度 性能
LEAD/LAG 窗口函数 窗口内偏移取历史行 月/周粒度数据,需连续排行 ★★☆☆☆ ⭐⭐⭐⭐
自关联 表和自身 JOIN 按时间条件 复杂多表联动/非连续时间 ★★★☆☆ ⭐⭐
DATE_SUB 偏移 构造历史日期再查表 临时查询/数据不连续 ★★☆☆☆ ⭐⭐

推荐:

  • 数据整齐、连续 → 用 LEAD/LAG 窗口函数,性能最好
  • 复杂场景多表联动 → 用 自关联
  • 临时调数据/老版本 MySQL → 用 DATE_SUB

划重点:窗口函数是首选方案,能解决大多数的同环比问题。

相关推荐
毛驴赶鹿7 小时前
【金仓数据库征文】KES V9性能调优实战记录:从慢SQL排查到全链路优化
数据库·sql
码农阿豪7 小时前
【金仓数据库征文】Mac开发环境|SpringBoot3 + MyBatisPlus对接KES V9 Docker实战全指南
数据库·macos·docker
杨云龙UP7 小时前
MySQL 数据库常用备份工具对比:mysqldump、mydumper 与 XtraBackup
linux·运维·服务器·数据库·mysql·dba·备份恢复
ZENERGY-众壹7 小时前
500个工商业电站压垮数据库?海量光伏时序数据存储的架构取舍
数据库·架构·influxdb·光伏·光伏运维
mubei-1238 小时前
SpringDAO的用法
java·开发语言·数据库
心灵Haven10 小时前
Centos7 + Mysql8.0
mysql·centos
星蓝_starblue11 小时前
零服务器、零数据库!开源growth-board,利用GitHub自动管理刷题/学习/求职全流程
服务器·数据库·程序人生·系统架构·node.js·github·改行学it
三84413 小时前
sqli-labs1-10通关笔记
数据库
BullSmall14 小时前
Another Redis Desktop Manager(ARDM)下载指南
数据库·redis·缓存
SelectDB15 小时前
Apache Doris 倒排索引工作原理:全文检索提速 59 倍,点查提速 14 倍
数据库