计算过去三个月中过去三个月的平均值

时间:2018-09-27 17:00:20

标签: tsql visual-studio-2012 reporting-services sql-server-2014

我正在使用SQL Server2014。我有一个像这样的表

   create table revenue (id varchar(2), trasdate date, revenue int);
   insert into revenue(id, trasdate, revenue)
   values ('aa', '2018/09/01', 1234.5),
   ('aa' , '2018/08/04', 450),
   ('aa', '2018/07/03',500),
   ('aa', '2018/06/04',600),
  ('ab', '2018/09/01', 1234.5),
  ('ab' , '2018/08/04', 450),
    ('ab', '2018/07/03',500),
    ('ab', '2018/06/04',600),
   ('ab', '2018/05/03', 200),
   ('ab', '2018/04/02', 150),
  ('ab', '2018/03/01', 350),
  ('ab', '2018/02/05', 700),
  ('aa', '2018/01/07', 400)
;

我正在准备一个SQL查询来创建SSRS报告。我想计算当前和过去3个月的过去3个月平均值,结果如下。因为我们现在在九月份。结果应显示如下:

**id    Period  Revenue_3Mon**
aa  March-May   233
aa  June-Aug    516
ab  March-May   233
ab  June-Aug    516

尽管我可以弄清楚“周期”列。我主要专注于获取Revenue_3Mon。因此,在经过谷歌搜索之后,我最初尝试使用以下查询。但是此查询在“行”附近引发错误,因为语法不正确;如果我从查询中删除行,则在关键字“之间”附近引发错误,因为语法不正确。而且i附近的语法不正确。

select i.id,i.mon,
   avg([i.mon_revenue]) over (partition by i.id, i.mon order by [i.id], 
  [i.mon] rows between 3  preceding and 1 preceding row) as revenue_3mon -- 
--  using 3 preceding and 1 preceding row you exclude the current row
 from (select a.id, month(a.trasdate) as mon,
         sum(a.revenue) as mon_revenue
  from revenue a
  group by a.id, month(a.trasdate)) i
 group by i.id, i.mon
 order by i.id,i.mon;

经过不懈的努力,我放弃了这个查询,并提出了一个新的解决方案,该解决方案与我的期望有点接近(经过大量的试验和错误)。

Declare @count as int;
declare @max as int;
set @count = 4
declare @temp as table (id varchar(2), monthoftrasdate int, revenue int, 
[3monavg] int);
SET @MAX = (SELECT distinct MAX(a.ROWNUM) FROM (SELECT id, month(trasdate) 
 as mon, SUM(revenue) TotalRevenue,
       -- sum(revenue) as mon_revenue,
       ROW_NUMBER() OVER(PARTITION BY ID ORDER BY MONTH(TRASDATE)) AS ROWNUM
        FROM revenue
       GROUP BY ID, MONTH(TRASDATE)         
        ) A GROUP BY A.ID);

     while (@count <= @max )
    begin

WITH CTE AS (
SELECT id, month(trasdate) as mon, SUM(revenue) TotalRevenue,
       -- sum(revenue) as mon_revenue,
        ROW_NUMBER() OVER(PARTITION BY ID ORDER BY MONTH(TRASDATE)) AS 
ROWNUM
FROM revenue
GROUP BY ID, MONTH(TRASDATE)
 )

  insert into @temp
  SELECT A.ID,A.MON, a.TotalRevenue
    ,( SELECT avg(b.TotalRevenue) as avgrev
    FROM CTE B
    WHERE B.ROWNUM BETWEEN  A.ROWNUM-3 AND A.ROWNUM-1
    AND A.ID = B.ID --AND A.mon = B.mon
    --and b.ROWNUM < a.ROWNUM
    and (a.mon > 3 and a.ROWNUM > 3)
    GROUP BY B.id

    ) AS REVENUE_3MON
  FROM CTE A

 set @count = @count + 1
 end

 select distinct a.* from @temp a

我必须使用'distinct'的原因是因为查询显示每个id和每个月的重复记录。到目前为止,结果显示如下

id  MonthofTrasdate Revenue 3MonAvg
aa  1                400    NULL
aa  2                700    NULL
aa  3                350    NULL
aa  4                150    483
aa  5                200    400
aa  6                600    233
aa  7                500    316
aa  8                450    433
aa  9               1234    516
ab  1                400    NULL
ab  2                700    NULL
ab  3                350    NULL
ab  4                150    483
ab  5                200    400
ab  6                600    233
ab  7                500    316
ab  8                450    433
ab  9               1234    516

这将拉出每个月过去3个月的平均值。但是我将按照我想要的方式来操纵SSRS上的其余部分。

目前,我的表格没有上一年的数据。这对我有用,现在显示接下来几个月的合适结果。但是我担心的是,当我不得不向明年的一月,二月和三月展示我的老板时,那么这几个月也应该能够像去年十月至十二月(前一年),十一月至一月以及十二月至二月一样拉动我。我正在努力找出将其放入我的查询的正确方法。

您能帮我解决这个问题吗?同时也请让我知道以前的查询出了什么问题。

3 个答案:

答案 0 :(得分:0)

我认为这应该做您想要的:

select r.*,
       avg(r.mon_revenue) over (partition by r.id
                                order by r.mon_min
                                rows between 3 preceding and 1 preceding row
                               ) as revenue_3mon 
--  using 3 preceding and 1 preceding row you exclude the current row
 from (select r.id, month(r.trasdate) as mon,
              min(r.trasdate) as mon_min,
              sum(r.revenue) as mon_revenue
       from revenue r
       group by r.id, year(r.trasdate), month(r.trasdate)
      ) 4
order by r.id, r.mon, r.mon_min;

注意:

  • 我修复了代码,以便它可以识别年份和日期。
  • 表达式[i.mon_revenue]不是有效的列引用(在您的情况下)。您没有名称为“ i.mon_revenue”的列(名称中带有.)。
  • 我将列别名更改为r以匹配表。
  • 我为每个月添加了一个日期列,以便于表达订单。
  • 不需要外部group by

答案 1 :(得分:0)

您的代码中存在几个语法错误。这应该给您您所需要的。内部查询很重要,但是希望这足以让您上路。

我将临时表转换为变量,并将收入列更改为INT,因为其中有十进制值,但原始样本表未更改

   DECLARE @revenue table (id varchar(2), trasdate date, revenue float)
   insert into @revenue(id, trasdate, revenue)
   values ('aa', '2018/09/01', 1234.5),
   ('aa' , '2018/08/04', 450),
   ('aa', '2018/07/03',500),
   ('aa', '2018/06/04',600),
  ('ab', '2018/09/01', 1234.5),
  ('ab' , '2018/08/04', 450),
    ('ab', '2018/07/03',500),
    ('ab', '2018/06/04',600),
   ('ab', '2018/05/03', 200),
   ('ab', '2018/04/02', 150),
  ('ab', '2018/03/01', 350),
  ('ab', '2018/02/05', 700),
  ('aa', '2018/01/07', 400)

SELECT 
        * 
    FROM
        ( 
          SELECT 
                *
                , MONTH(trasdate) as MonthNumber
                , AVG(revenue) OVER (PARTITION BY id
                                     ORDER BY 
                                        id
                                        , MONTH(trasdate) ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING) as ThreeMonthAvg
            FROM @revenue
        ) a
    WHERE MONTH(GETDATE()) - MonthNumber IN (0, 3, 6, 9)

这给出了以下结果

aa  2018-06-04  600     6   400
aa  2018-09-01  1234.5  9   516.666666666667
ab  2018-03-01  350     3   700
ab  2018-06-04  600     6   233.333333333333
ab  2018-09-01  1234.5  9   516.666666666667

答案 2 :(得分:0)

您第一次尝试时遇到的问题:

  • 您已将一些别名和列名括在[i.mon_revenue]之类的方括号中。不需要方括号,但是如果要使用方括号,则必须将其在点[i].[mon_revenue]处打散。
  • 在窗口函数表达式中,最后一个太多。
  • 窗口函数在最后(在相应查询的其余部分之后)应用,因此您还必须在外部查询的GROUP BY子句中包括i.mon_revenue
  • 知道内部查询每idmon会产生一行,因此 id-mon 分区中永远不会有前面的行。因此,您不能同时按id进行分区。

要在解决问题后简化查询:按分区列排序通常是没有意义的,并且由于-如已提到的,内部查询返回唯一的 id-mon 组合,因此您无需必须在外部查询中对这些分组。查看该查询,我们看到外部查询只是直接选择并使用内部查询中的值,从而不必在两个查询中进行分隔。因此,实际上,您想执行以下查询,这将产生连续3个月的平均值(我也添加了每月TotalRevenue的时间):

SELECT id, MONTH(trasdate) AS mon, SUM(revenue) AS TotalRevenue,
   AVG(SUM(revenue)) OVER (PARTITION BY id ORDER BY MONTH(trasdate) ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING) AS revenue_3mon
FROM revenue
GROUP BY id, MONTH(trasdate)
ORDER BY id, MONTH(trasdate);

第二次尝试的建议:

  • 在计算@MAX的值时,您依赖于每个id拥有相同月份数的收入这一事实。确定吗?
  • WHILE循环内的代码不依赖于@count,因此它将多次将相同的数据添加到@temp表中,这可能是您认为需要DISTINCT的原因。因此:不需要变量,不需要循环和@temp,不需要DISTINCT。
  • 条件A.mon > 3A.rownum > 3对您当前的数据来说是多余的。一般而言,我想您不想明确表示从1月到3月的月份,因此应该删除A.mon > 3A.rownum > 3也可以删除,除非您确实不希望在前两个月或更短的时间内看到三个月的平均值。
  • 由于平均值的子查询只限于一个id,因此不需要GROUP BY。
  • 由于ROW_NUMBER函数并不关心月份中的间隔,因此我建议使用其他编号函数,例如DATEDIFF(month, MAX(trasdate), GETDATE()) AS mnum。当然,然后必须将子查询的WHERE子句中的比较更改为B.mnum BETWEEN A.mnum+1 AND A.mnum+3

因此,您的第二次尝试可以简化为这种尝试,至少在样本数据不存在月份差距的情况下,这将产生与上述相同的结果:

WITH CTE AS (
    SELECT id, MONTH(trasdate) AS mon, SUM(revenue) AS TotalRevenue,
        DATEDIFF(month, MAX(trasdate), GETDATE()) AS mnum
    FROM revenue
    GROUP BY id, MONTH(trasdate)
)
SELECT id, mon, TotalRevenue
  , (SELECT AVG(B.TotalRevenue)
     FROM CTE B
     WHERE B.mnum BETWEEN A.mnum+1 AND A.mnum+3
       AND A.id = B.id
    ) AS revenue_3mon
FROM CTE A
ORDER BY id, mnum DESC;

现在,猜猜是什么,像我的mnum这样使用DATEDIFF的表达式随着我们移到过去而每月增加一个,而与年份无关,所以这对于将好吧,是否要(或可以?)使用Window功能:

具有OVER()

SELECT id, MONTH(MIN(trasdate)) AS mon, YEAR(MIN(trasdate)) AS yr, SUM(revenue) AS TotalRevenue,
   AVG(SUM(revenue)) OVER (PARTITION BY id ORDER BY MIN(trasdate) ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING) AS revenue_3mon
FROM revenue
GROUP BY id, DATEDIFF(month, trasdate, GETDATE())
ORDER BY id, DATEDIFF(month, trasdate, GETDATE()) DESC;

没有OVER()

WITH CTE AS (
    SELECT id, MIN(trasdate) AS min_dt, SUM(revenue) AS TotalRevenue,
        DATEDIFF(month, trasdate, GETDATE()) AS mnum
    FROM revenue
    GROUP BY id, DATEDIFF(month, trasdate, GETDATE())
)
SELECT id, MONTH(min_dt) AS mon, YEAR(min_dt) AS yr, TotalRevenue
  , (SELECT AVG(B.TotalRevenue)
     FROM CTE B
     WHERE B.mnum BETWEEN A.mnum+1 AND A.mnum+3
       AND A.id = B.id
    ) AS revenue_3mon
FROM CTE A
ORDER BY id, mnum DESC;

两个查询都允许检索每个时间段(包括月和年)的最小和最大日期。

如果您想使用最初在下发布的内容,结果应显示类似的内容(只需按照之前的三个月时间间隔进行分组),则只需将原始revenue分组由id(DATEDIFF(month, trasdate, GETDATE())-1)/3组成的表格(过滤WHERE DATEDIFF(month, trasdate, GETDATE()) > 0)。如果是这样,报表服务器当然也可以完成这种分组和聚合。