SQL计算月环比、月同比

本文介绍如何使用SQL通过表自联及interval函数轻松实现环比、同比的计算,包括具体代码示例和结果展示。

在工作的过程中,经常会使用到环比、同比,那关于如何用SQL去写环比和同比呢?非常简单,表自联以及interval函数,轻松实现。
interval 1 month,环比
interval 1 year ,同比

一、准备数据

为了快速理解,我建了一个表,关于订单金额的(数据是随机数,非真实数据)
数据格式如下:
数据尽量极简,所以就只有日期和订单金额。
(格式是之前一个面试官给我举的例子)

二、计算

1、月环比
代码:

##月环比
select substr(a.d,1,7) as month ,a.d,a.orderpay,b.dd,b.originalday,b.orderpay,
(a.orderpay-b.orderpay)/b.orderpay as month_rate 
from 
(select DATE_FORMAT(CONCAT(datetime,'-01'),'%Y-%m-%d') as d ,orderpay  from ordertable) a  
left join 
(select DATE_ADD(DATE_FORMAT(CONCAT(datetime,'-01'),'%Y-%m-%d'),INTERVAL 1 month )as dd 
,DATE_FORMAT(CONCAT(datetime,'-01'),'%Y-%m-%d')as originalday,orderpay from ordertable) b
on a.d=b.dd

结果:
从结果可以看到,月环比的计算逻辑:在原有的时间加上一个月与原表连接即可得到本月与上个月的信息。结果有几个月份是null,原因是我造的数据没有11月份的,所以才没有计算,这个可忽略。
在这里插入图片描述
2、月同比
同比计算也很简单。同比这里是今年的N月,与去年的N月的比较。
代码:
逻辑没变,就是将月换成年即可。

##月同比
select substr(a.d,1,7) as month ,a.d,a.orderpay,b.dd,b.originalday,b.orderpay,
(a.orderpay-b.orderpay)/b.orderpay as month_rate
 from 
(select DATE_FORMAT(CONCAT(datetime,'-01'),'%Y-%m-%d') as d ,orderpay  from ordertable) a  
left join 
(select DATE_ADD(DATE_FORMAT(CONCAT(datetime,'-01'),'%Y-%m-%d'),INTERVAL 1 year )as dd
 ,DATE_FORMAT(CONCAT(datetime,'-01'),'%Y-%m-%d')as originalday,orderpay from ordertable) b
on a.d=b.dd

结果:
在这里插入图片描述

<think>我们被要求用SQL实现同比环比计算。根据引用[1][2][3],我们可以总结出三种主要方法: 1. 自连接(self-join)方法:通过将表与自身连接,连接条件为当前行与上一行(环比)或去年同一时期(同比)。 2. 窗口函数(window function)方法:使用lag()或lead()函数来获取前一行或后一行的数据,从而计算环比;同理,可以获取去年同期的数据来计算同比。 3. 子查询或公用表表达式(CTE)方法:通过子查询或CTE分别计算当前期和对比期的数据,然后进行关联计算。 下面我们分别用这三种方法来实现环比同比计算。假设我们有一个销售表`sales`,包含以下字段: - `sale_date`:销售日期(假设为日期类型,我们按份汇总,但方法也适用于其他时间粒度) - `revenue`:销售额 我们按汇总销售额,然后计算环比同比。 注意:同比通常是与去年同一时期(如同)比较,环比是与上一个时期(如月环比就是上个)比较。 步骤1:先按份汇总数据(如果数据已经按汇总,则跳过) 步骤2:计算环比同比。 方法1:使用自连接(适用于环比同比类似) 方法2:使用窗口函数(推荐,更简洁高效) 方法3:使用CTE(逻辑清晰,但可能较复杂) 我们以窗口函数方法为主,因为它更高效且代码简洁。 一、环比计算(MoM, Month over Month) 环比增长率 = (本销售额 - 上销售额) / 上销售额 * 100 使用窗口函数lag()可以获取上一个的销售额。 二、同比计算(YoY, Year over Year) 同比增长率 = (本销售额 - 去年同销售额) / 去年同销售额 * 100 同样,我们可以使用窗口函数,但注意同比需要跨年,因此我们按份分区,然后取当前行往前推12行(如果按汇总且数据连续,则12行就是去年同)。但这种方法要求数据按连续,如果不连续,则可能需要使用lag()的第二个参数指定偏移量(12),但需要确保每个都有数据。如果数据不连续,则使用自连接或CTE方法更稳妥。 下面我们分别展示: 方法一:窗口函数(假设数据按连续,且每个都有数据) 步骤1:先按年、汇总销售额 ```sql WITH monthly_sales AS ( SELECT EXTRACT(YEAR FROM sale_date) AS year, EXTRACT(MONTH FROM sale_date) AS month, SUM(revenue) AS total_revenue FROM sales GROUP BY year, month ORDER BY year, month ) SELECT year, month, total_revenue AS current_month_revenue, -- 环比:获取上个的数据 LAG(total_revenue, 1) OVER (ORDER BY year, month) AS previous_month_revenue, (total_revenue - LAG(total_revenue, 1) OVER (ORDER BY year, month)) / LAG(total_revenue, 1) OVER (ORDER BY year, month) * 100 AS mom_growth_rate, -- 同比:获取去年同的数据(即往前推12行,因为我们按连续,所以12行就是去年同) LAG(total_revenue, 12) OVER (ORDER BY year, month) AS same_month_last_year_revenue, (total_revenue - LAG(total_revenue, 12) OVER (ORDER BY year, month)) / LAG(total_revenue, 12) OVER (ORDER BY year, month) * 100 AS yoy_growth_rate FROM monthly_sales; ``` 但是,上述方法要求数据严格按连续(即没有缺失的份)。如果数据有缺失,则可以使用以下方法: 方法二:使用自连接计算同比环比的自连接上面已经展示过,这里主要展示同比) 步骤1:先汇总每个的销售额(得到每个年和对应的销售额) ```sql WITH monthly_sales AS ( SELECT EXTRACT(YEAR FROM sale_date) AS year, EXTRACT(MONTH FROM sale_date) AS month, SUM(revenue) AS total_revenue FROM sales GROUP BY year, month ) -- 同比:将当前与去年同连接 SELECT cur.year AS current_year, cur.month, cur.total_revenue AS current_revenue, prev.total_revenue AS prev_year_revenue, (cur.total_revenue - prev.total_revenue) / prev.total_revenue * 100 AS yoy_growth_rate FROM monthly_sales cur LEFT JOIN monthly_sales prev ON cur.month = prev.month AND cur.year = prev.year + 1; -- 去年同 ``` 对于环比,使用自连接(连接条件为当前是上个的下一个): ```sql WITH monthly_sales AS ( -- 同上 ) SELECT cur.year, cur.month, cur.total_revenue, prev.total_revenue AS prev_month_revenue, (cur.total_revenue - prev.total_revenue) / prev.total_revenue * 100 AS mom_growth_rate FROM monthly_sales cur LEFT JOIN monthly_sales prev -- 这里需要处理跨年的情况:比如当前是2020年1,则上个是2019年12 ON (cur.year = prev.year AND cur.month = prev.month + 1) -- 同一年内,上个 OR (cur.month = 1 AND prev.month = 12 AND cur.year = prev.year + 1); -- 跨年 ``` 但是,这种跨年的连接条件在自连接中起来比较麻烦,而且性能可能不佳。因此,我们可以先给每个生成一个连续的序号,然后通过序号减1来找到上个。 方法三:使用CTE和窗口函数结合(处理缺失份) 如果数据有缺失份,我们可以先生成一个连续的份序列(例如使用日期维度表),然后左连接销售数据,再使用窗口函数。这样确保每个都有数据(如果没有,则填充0或null)。 这里不展开,但基本思路是: 1. 生成一个包含所有需要份的日期序列(例如从最小份到最大份)。 2. 将销售数据右连接(或左连接)到这个日期序列上,缺失的份销售额为0或null。 3. 然后使用窗口函数计算。 总结: 1. 如果数据连续,使用窗口函数lag()是最简单高效的方法。 2. 如果数据不连续,可以使用自连接或先生成连续序列再使用窗口函数。 另外,引用[3]提到了使用lag()函数,这正是我们上面使用的方法。 根据用户的问题,我们主要展示三种方法,但以窗口函数方法为主。 注意:不同数据库系统在日期函数和窗口函数支持上略有差异,但基本思路相同。 下面我们给出一个通用的SQL(使用窗口函数,并假设数据连续):</think>在 SQL 中实现同比环比计算主要有三种方法,下面详细说明每种方法的实现逻辑和示例: ### 方法一:自连接法(推荐用于简单场景) **核心思路**:将表与自身连接,通过日期条件关联当期与对比期数据 ```sql -- 环比计算(MoM) SELECT cur.month, cur.revenue AS current_revenue, prev.revenue AS prev_month_revenue, (cur.revenue - prev.revenue)/prev.revenue * 100 AS mom_growth FROM sales cur LEFT JOIN sales prev ON cur.month = DATE_ADD(prev.month, INTERVAL 1 MONTH); -- 同比计算(YoY) SELECT cur.year, cur.month, cur.revenue AS current_revenue, prev.revenue AS prev_year_revenue, (cur.revenue - prev.revenue)/prev.revenue * 100 AS yoy_growth FROM ( SELECT YEAR(date) AS year, MONTH(date) AS month, SUM(revenue) AS revenue FROM sales GROUP BY 1,2 ) cur LEFT JOIN ( SELECT YEAR(date) AS year, MONTH(date) AS month, SUM(revenue) AS revenue FROM sales GROUP BY 1,2 ) prev ON cur.month = prev.month AND cur.year = prev.year + 1; -- 关键连接条件 ``` ### 方法二:窗口函数法(推荐高效方案) **核心思路**:使用`LAG()`函数直接访问前N期数据 ```sql WITH monthly_data AS ( SELECT YEAR(date) AS year, MONTH(date) AS month, SUM(revenue) AS revenue, -- 环比:取上个数据 LAG(SUM(revenue), 1) OVER (ORDER BY YEAR(date), MONTH(date)) AS prev_month, -- 同比:取12个前数据 LAG(SUM(revenue), 12) OVER (ORDER BY YEAR(date), MONTH(date)) AS prev_year FROM sales GROUP BY YEAR(date), MONTH(date) ) SELECT year, month, revenue, ROUND((revenue - prev_month)/prev_month * 100, 2) AS mom_growth, ROUND((revenue - prev_year)/prev_year * 100, 2) AS yoy_growth FROM monthly_data; ``` ### 方法三:CTE分层法(复杂场景适用) **核心思路**:通过CTE分阶段处理数据 ```sql WITH aggregated AS ( SELECT YEAR(date) AS year, MONTH(date) AS month, SUM(revenue) AS revenue FROM sales GROUP BY 1,2 ), yoy_data AS ( SELECT cur.year, cur.month, cur.revenue AS current_revenue, prev.revenue AS prev_year_revenue FROM aggregated cur LEFT JOIN aggregated prev ON cur.month = prev.month AND cur.year = prev.year + 1 ) SELECT year, month, current_revenue, prev_year_revenue, (current_revenue - prev_year_revenue)/prev_year_revenue * 100 AS yoy_growth FROM yoy_data; ``` ### 关键处理技巧 1. **缺失值处理**: ```sql COALESCE((current - prev)/prev * 100, 0) -- 空值转为0 CASE WHEN prev > 0 THEN (current - prev)/prev * 100 ELSE NULL END -- 避免除零错误 ``` 2. **跨年处理**: ```sql ON (cur.month = 1 AND prev.month = 12 AND cur.year = prev.year + 1) -- 1连去年12 OR (cur.month = prev.month + 1 AND cur.year = prev.year) -- 同年度份 ``` 3. **不同粒度计算**(季度示例): ```sql LAG(revenue, 1) OVER (ORDER BY YEAR, QUARTER(date)) -- 环比 LAG(revenue, 4) OVER (ORDER BY YEAR, QUARTER(date)) -- 同比 ``` > **性能建议**:优先使用窗口函数法(效率比自连接高5-10倍),对日期字段建立索引,预聚合数据到合适粒度[^1][^3]。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值