SQL 移动聚合
Posted
技术标签:
【中文标题】SQL 移动聚合【英文标题】:SQL Moving Aggregation 【发布时间】:2015-02-24 17:35:14 【问题描述】:问题:对于包含日期列、任意数量的类别列和值列的给定记录集,我想计算任意日期窗口的值聚合,例如 30 天、365 天等。我查看了窗口聚合函数、CTE 和其他一些函数,但它们似乎(至少对我而言)无法执行所需的功能。
下面的 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
谢谢。
【问题讨论】:
您是否按照小提琴的建议使用 SQL-Server 2008? 我目前使用的是 SQL-Server 2008,但这可能是未来的 Teradata 查询。 【参考方案1】:对 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 的魔力),以便在我的笔记本电脑上获得更长的持续时间。 (很难比较 1ms 和 1ms =)
令人惊讶的是,Halt CO 的(已接受)解决方案返回的结果与原始查询(或“我的”查询)不同“扩展”测试集;你可能想调查一下! (原因是它多次找到基准日期,因此多次求和,然后最终得到更大的计数和 SumValues。我不确定这是你想要的,或者它是否是什么'真实数据'可能会发生这种情况,但是由于您在该 foo 表上放置了索引而不是 UNIQUE 索引,因此我假设可能会出现双精度...)
【讨论】:
【参考方案2】:在我的简短测试中,如果 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()
版本快一点。
【讨论】:
我添加了索引并在本地测试。原解决方案:1108ms CPU,经过了 179ms。建议的解决方案:1841 毫秒 CPU,经过 512 毫秒。原始解决方案 foo 表上的 IO:扫描计数 12,逻辑读取 178,工作表扫描计数 8,逻辑读取 66862,建议的解决方案:foo 扫描计数 10,逻辑读取 31,工作表扫描计数 836 逻辑读取 244163。考虑到 io 结果,更具可扩展性的解决方案? @user3092841 原始查询是否已缓存?我看到性能提高的原因是,由于在比较中使用了dt
而不是DATEDIFF()
,我得到了索引查找而不是索引扫描,但无论哪种方式,你都会失去一些好处,因为你需要一个范围。
我更新了 SQL Fiddle [链接] (sqlfiddle.com/#!3/2ba2f4/4) 计划几乎相同。 link VS link 非常感谢你的帮助,我想也许有一个数量级的解决方案,但似乎没有。
@user3092841 是的 sql fiddle 计划与我看到的大致相同,我只看到我的 sql server 实例上显示的更详细计划的差异。暂时想不出任何其他重大改进。以上是关于SQL 移动聚合的主要内容,如果未能解决你的问题,请参考以下文章