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 移动聚合的主要内容,如果未能解决你的问题,请参考以下文章

KeyMob-移动广告聚合平台,移动聚合广告的未来!

选移动广告聚合平台,我选KeyMob!KeyMob移动广告聚合平台

国内领先的移动广告聚合平台-KeyMob移动广告聚合平台

KeyMob聚合平台--国内优质移动广告聚合平台

KeyMob移动聚合平台--领先的广告聚合平台

KeyMob移动聚合平台--领先的广告聚合平台