我正在使用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上的其余部分。
目前,我的表格没有上一年的数据。这对我有用,现在显示接下来几个月的合适结果。但是我担心的是,当我不得不向明年的一月,二月和三月展示我的老板时,那么这几个月也应该能够像去年十月至十二月(前一年),十一月至一月以及十二月至二月一样拉动我。我正在努力找出将其放入我的查询的正确方法。
您能帮我解决这个问题吗?同时也请让我知道以前的查询出了什么问题。
答案 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]
处打散。i.mon_revenue
。id
和mon
会产生一行,因此 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
拥有相同月份数的收入这一事实。确定吗?@count
,因此它将多次将相同的数据添加到@temp
表中,这可能是您认为需要DISTINCT的原因。因此:不需要变量,不需要循环和@temp
,不需要DISTINCT。A.mon > 3
和A.rownum > 3
对您当前的数据来说是多余的。一般而言,我想您不想明确表示从1月到3月的月份,因此应该删除A.mon > 3
。 A.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
)。如果是这样,报表服务器当然也可以完成这种分组和聚合。