2

问题:对于包含日期列,任意数量的类别列和值列的给定记录集,我想计算任意日期窗口的值的聚合,例如30天,365天等等。我已经看过窗口集合函数,CTE和其他一些函数,但它们并没有出现(至少对我来说)来执行所需的功能。SQL移动聚合

下面的SQL(T-SQL)代表了我试图完成的基本概念,但是我对它的可伸缩性,特别是连接有不好的感觉,并且一旦我尝试按其他名义组进行分组,就会遇到困难。

SELECT 
     basedate 
     , count(*) as [n] 
     , sum(Value) as [SumValue] 
     , avg(value) As [AverageValue] 
     , stdev(value) As [StdevValue] 
FROM 
    (SELECT t1.basedate , t2.* 
    FROM 
     (SELECT DISTINCT dt as basedate from foo)as t1 
     ,foo as t2 
     WHERE datediff(d, t1.basedate, t2.dt) between -30 and 0 
    ) t3 
GROUP BY t3.basedate 
ORDER BY t3.BASEDATE DESC 

我创建了一个SQLFiddle来试图使它更加具体。

SQLFiddle

谢谢。

+0

您是否使用SQL Server 2008作为你的小提琴建议? – 2015-02-24 17:45:50

+0

我目前正在使用SQL-Server 2008,但这可能是一条Teradata查询。 – user3092841 2015-02-24 17:47:43

回答

0

在SqlFiddle中提供的设置中玩了一番之后,我来到了这两个潜在的解决方案:(好吧,第一个解决方案只有一半,不知道如何将stdev()添加到高效路)

WITH t1 
    AS (SELECT DISTINCT dt as basedate from foo), 
    sumcount 
    AS (SELECT basedate, 
      SUM((CASE WHEN datediff(d, t1.basedate, t2.dt) between -30 and 0 THEN 1 ELSE 0 END)) as [n], 
      SUM((CASE WHEN datediff(d, t1.basedate, t2.dt) between -30 and 0 THEN value ELSE 0 END)) as [Sumvalue] 
     FROM t1, foo t2 
     GROUP BY basedate) 
SELECT basedate, 
     [n], 
     [Sumvalue], 
     [Sumvalue]/[n] as [Averagevalue] 
    FROM sumcount 
ORDER BY basedate DESC 


GO 

WITH t1 
    AS (SELECT DISTINCT dt as basedate from foo), 
    t2 
    AS (SELECT basedate, min_date = DateAdd(day, -30, basedate), max_date = DateAdd(day, 0, basedate) from t1) 

SELECT basedate, 
      count(*) as [n] 
     , sum(b.value) as [Sumvalue] 
     , avg(b.value) As [Averagevalue] 
     , stdev(b.value) As [Stdevvalue] 
FROM t2 
JOIN foo b 
    ON b.dt BETWEEN t2.min_date AND t2.max_date 
GROUP BY basedate 
ORDER BY basedate DESC 

我喜欢最后一个为它的简单可读性,并且巧合的运行相当快一点过,虽然我不能完全知道为什么呢。请注意,我将测试数据加载了100次(使用GO 100的魔法),以便在笔记本电脑上获得更长的持续时间。 (这是很难比较1毫秒VS 1毫秒=)

Query Plan Explorer Screenshot

令人惊讶的在(接受)的解决方案从暂停CO 比原来的查询返回不同的结果(或 '我' 查询)时给定'扩展'测试集;你可能想看看! (原因在于它多次查找基准日期,因此导致了多次累加,然后最终得到了更大的Counts和SumValues。我不确定这是你想要的,还是它是某种东西这可能发生在'真实数据',但由于你把一个索引,而不是一个独特的索引,我假设双打可以发生......)

0

在我简短的测试,这是比你当前的查询速度更快,如果dt字段建立索引:

SELECT a.dt AS basedate 
     , count(*) as [n] 
     , sum(b.Value) as [SumValue] 
     , avg(b.value) As [AverageValue] 
     , stdev(b.value) As [StdevValue] 
FROM foo a 
JOIN foo b 
    ON b.dt BETWEEN DATEADD(DAY,-30,a.dt) AND a.dt 
GROUP BY a.dt 
ORDER BY a.dt DESC 

编辑:我是因为在SQL Server被问及版2012+有一个为RANGE/ROWS支持,可以创建一个你喜欢的移动窗口,我相信你会陷入一种自我加入。使用DATEADD()并比较dt的值稍微快于DATEDIFF()版本。

+0

我添加了索引并在本地进行了测试。原始解决方案:1108ms CPU,179ms已过。建议的解决方案:1841毫秒CPU,512毫秒已过。在原始解决方案foo表上的IO:扫描计数12,逻辑读取178,工作表扫描计数8,逻辑读取66862,提议的解决方案:foo扫描计数10,逻辑读取31,工作表扫描计数836逻辑读取244163。考虑到io结果,更具可扩展性的解决方案? – user3092841 2015-02-24 19:05:52

+0

@ user3092841原始查询是否已被缓存?我看到的性能提高的原因是,由于在比较中使用了'dt'而不是'DATEDIFF()',所以我得到了索引查找而不是索引扫描,但是无论哪种方式,您都会失去一些好处,因为您需要一个范围。 – 2015-02-24 19:09:02

+0

我更新了SQL小提琴[链接](http://www.sqlfiddle.com/#!3/2ba2f4/4)计划几乎相同。 [链接](http://www.sqlfiddle.com/#!3/2ba2f4/4/0)VS [链接](http://www.sqlfiddle.com/#!3/2ba2f4/4/1)I非常感谢你对此的帮助,我想也许有一个数量级的解决方案,但似乎没有。 – user3092841 2015-02-24 19:23:15