前 3 个月的滚动总和 SQL Server

Posted

技术标签:

【中文标题】前 3 个月的滚动总和 SQL Server【英文标题】:Rolling sum previous 3 months SQL Server 【发布时间】:2017-01-21 13:54:26 【问题描述】:

我的表结构如下:

Name           |Type
---------------|-----
fiscal year    | varchar
period         | varchar
country        | varchar
value          | int

我在 SQL Server 2012 中遇到了一个查询,该查询应该计算表中每个不同月份的前三个月的总和。月份之间可能存在间隔,每个月的数据都不存在,并且对于给定的年份、月份和国家/地区可能有几行。

EG:

Year    |Period |Country   |Value
--------|-------|----------|------
2016    |2      |Morovia   |100
2016    |9      |Morovia   |100
2016    |10     |Elbonia   |-20
2016    |10     |Elbonia   |2000
2016    |10     |Elbonia   |200
2016    |10     |Elbonia   |-100
2016    |10     |Elbonia   |1000
2016    |10     |Morovia   |200
2016    |10     |Elbonia   |-200
2016    |10     |Elbonia   |-200
2016    |10     |Elbonia   |100
2016    |10     |Elbonia   |60
2016    |10     |Elbonia   |40
2016    |11     |Morovia   |200
2016    |11     |Elbonia   |100

我正在尝试创建一个如下所示的结果集:

Year    |Period |Country   |3M Value
--------|-------|----------|--------
2016    |2      |Morovia   |100        - data only for this month
2016    |9      |Morovia   |100        - data only for this month
2016    |10     |Morovia   |300        - current month (200) + previous(100)
2016    |10     |Elbonia   |2880       - data only for this month
2016    |11     |Morovia   |500        - current + previous + 2 month ago
2016    |11     |Elbonia   |2980       - current month(100) + previous(2880)

【问题讨论】:

【参考方案1】:

使用Outer JOIN的另一种方法

;WITH cte
     AS (SELECT year,
                period,
                country,
                Sum(value) AS sumvalue
         FROM   Yourtable 
         GROUP  BY year,
                   period,
                   country)
SELECT a.Year,
       a.Period,
       a.Country,
       a.sumvalue + Isnull(Sum(b.sumvalue), 0) as [3M Value]
FROM   cte a
       LEFT JOIN cte b
              ON a.Country = b.Country
                 AND Datefromparts(b.[Year], b.Period, 1) IN ( Dateadd(mm, -1, Datefromparts(a.[Year], a.Period, 1)), Dateadd(mm, -2, Datefromparts(a.[Year], a.Period, 1)) )
GROUP  BY a.Year,
          a.Period,
          a.Country,
          a.sumvalue 
Live Demo

【讨论】:

【参考方案2】:

如果月份不可用,则可以使用非等值连接或外部应用:

with t as (
      select year, period, country, sum(value) as sumvalue
      from t
      group by year, period, country
     )
select t.*, tt.sumvalue_3month
from t outer apply
     (select sum(t2.sumvalue) as sumvalue_3month
      from t t2
      where t.country = t2.country and
            t2.year * 12 + t2.period >= t.year * 12 + t.period - 2 and
            t2.year * 12 + t2.period <= t.year * 12 + t.period
     ) tt;

CTE 按年、期和月汇总数据。 outer apply 然后是前三个月的总和。诀窍是将年份和期间值转换为从零开始的月份。

其实更简单的方法是在CTE中做yyyymm计算:

with t as (
      select year, period, country, sum(value) as sumvalue,
             (t.year * 12 + t.period) as month_count
      from t
      group by year, period, country
     )
select t.*, tt.sumvalue_3month
from t outer apply
     (select sum(t2.sumvalue) as sumvalue_3month
      from t t2
      where t.country = t2.country and
            t2.month_count >= t.month_count - 2 and
            t2.month_count <= t.month_count
     ) tt;

【讨论】:

@SQLZim 。 . .谢谢你。这个国家对于 OP 的预期产出显然很重要。 没问题。 where 子句中的那种尾随 and 的风格从何而来? 我缩进 SQL SELECT 查询,以便主要 SQL 子句在 LEFT 上对齐。 AND(就此而言,JOINON)不是主要子句(想想SELECTFROMWHEREGROUP BY 等)。【参考方案3】:

假设您有另一个包含国家/地区的表

CREATE TABLE Country(Country VARCHAR(50) PRIMARY KEY);
INSERT INTO Country VALUES ('Morovia'),('Elbonia');

然后您可以创建一个包含感兴趣时间段的所有年/月组合的表格

CREATE TABLE YearMonth
(
Year INT,
Month INT,
PRIMARY KEY (Year, Month)
)
INSERT INTO YearMonth
SELECT Year, Month
FROM (VALUES(2015), (2016), (2017)) Y(Year)
CROSS JOIN (VALUES(1), (2), (3),(4), (5), (6),(7), (8), (9), (10), (11), (12)) M(Month)

然后外连接到 (Demo) 上,如下所示。对于每个国家/地区,保证YearMonth 涵盖的每个月每年都会有一行,然后您可以按国家/地区划分并使用ROWS BETWEEN 2 PRECEDING AND CURRENT ROW 对它们求和。

WITH Grouped AS
( SELECT  SUM(Value) AS MonthValue,
                  Year,
                  Period,
                  Country
         FROM     Table1
         GROUP BY Year,
                  Period,
                  Country ), Summed AS
( SELECT    YM.*,
                      C.Country,
                      G.Year AS GYear,
                      [3M Value] = SUM(MonthValue) OVER (partition BY C.Country 
                                                   ORDER BY YM.Year, YM.Month 
                                                   ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)
           FROM       YearMonth YM
           CROSS JOIN Country C
           LEFT JOIN  Grouped G
           ON         G.Year = YM.Year
                      AND G.Period = YM.Month
                      AND G.Country = C.Country )
SELECT   *
FROM     Summed
WHERE    GYear IS NOT NULL
ORDER BY Year,
         Month,
         Country

或具有相同语义但可能更高效的版本

SELECT      CA.*
FROM        Country C
CROSS APPLY
(
SELECT    YM.*,
          C.Country,
          G.Year AS GYear,
          [3M Value] = SUM(MonthValue) OVER ( ORDER BY YM.Year, YM.Month 
                                              ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)
FROM      YearMonth YM
LEFT JOIN 
(
SELECT SUM(Value) AS MonthValue,
       Year,
       Period
FROM   Table1 T
WHERE T.Country = C.Country
GROUP  BY Year,
          Period) G
ON        G.Year = YM.Year
          AND G.Period = YM.Month
                     ) CA
 WHERE   GYear IS NOT NULL
ORDER BY    Year,
            Month

【讨论】:

ROWS BETWEEN 3 PRECEDING AND CURRENT ROW 将考虑前 3 行,即使月份缺失。请在演示中添加(2017, 1, 'Elbonia', 100) 行并查看结果。 @vkp - 这就是为什么在没有丢失月份的表上有一个外连接。这和分组确保在所涵盖的期间每个月都有一行。 确定 Martin .. 但你只是交叉加入国家之间的全年月份组合,这将省略一些缺失的月份。希望你明白我的意思。 @vkp - 不抱歉。我已将您想要添加到演示中的行添加。你认为结果有什么问题? rextester.com/BKKM67176 啊它应该是前 2 行 + 当前行 3 个月。是这样吗?

以上是关于前 3 个月的滚动总和 SQL Server的主要内容,如果未能解决你的问题,请参考以下文章

如何根据 groupby 计算 12 个月的滚动总和?

前 3 个月的值总和

SQL Server:如何为同一张表中的不同客户获取 3 天内的滚动总和

Qlikview 滚动 12 个月财政

如何找到滚动的 3 个月方差?

动态 SQL 滚动 12 个月数据透视