【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

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

相关推荐
qq_333120972 小时前
Sql Server数据库远程连接访问配置
数据库
yaodong5182 小时前
PostgreSQL_安装部署
数据库·postgresql
eEKI DAND2 小时前
SQL美化器:sql-beautify安装与配置完全指南
数据库·sql
nbwenren2 小时前
MySQL中日期和时间戳的转换:字符到DATE和TIMESTAMP的相互转换
数据库·mysql
SPC的存折2 小时前
10、Docker容器故障排查
linux·运维·数据库·docker·容器
heRs BART2 小时前
Redis简介、常用命令及优化
数据库·redis·缓存
NiKick3 小时前
MySql中的事务、MySql事务详解、MySql隔离级别
数据库·mysql·adb
gmaajt3 小时前
mysql如何检查数据库表是否存在损坏_使用CHECK TABLE命令修复
jvm·数据库·python
heRs BART3 小时前
【Flask】四、flask连接并操作数据库
数据库·python·flask