从同一数据集中连接不同时期的总和

时间:2017-10-27 22:36:49

标签: sql sql-server aggregate sql-tuning

我经常遇到需要比较来自同一来源的不同时期的汇总数据的情况。

我通常以这种方式处理它:

SELECT
    COALESCE(SalesThisYear.StoreId, SalesLastYear.StoreId) StoreId
    , SalesThisYear.Sum_Revenue RevenueThisYear
    , SalesLastYear.Sum_Revenue RevenueLastYear
FROM
    (
        SELECT   StoreId, SUM(Revenue) Sum_Revenue
        FROM     Sales
        WHERE    Date BETWEEN '2017-09-01' AND '2017-09-30'
        GROUP BY StoreId
    ) SalesThisYear
    FULL JOIN (
        SELECT   StoreId, SUM(Revenue) Sum_Revenue
        FROM     Sales
        WHERE    Date BETWEEN '2016-09-01' AND '2016-09-30'
        GROUP BY StoreId
    ) SalesLastYear
    ON (SalesLastYear.StoreId = SalesThisYear.StoreId)

-- execution time 337 ms

在我看来,它不是很优雅,因为它访问了两次表,但它确实有效。

另一种实现相同目的的方法是:

SELECT
    Sales.StoreId
    , SUM(CASE YEAR(Date) WHEN 2017 THEN Revenue ELSE 0 END) RevenueThisYear
    , SUM(CASE YEAR(Date) WHEN 2016 THEN Revenue ELSE 0 END) RevenueLastYear
FROM
    Sales
WHERE
    Date BETWEEN '2017-09-01' AND '2017-09-30' 
    or Date BETWEEN '2016-09-01' AND '2016-09-30'
GROUP BY
    StoreId

-- execution time 548 ms

两个解决方案在我的数据集上执行的几乎相同(所选时段中的1,929,419行,其所有位置的索引),第一个在时间上稍好一些。如果我包含更多句点并不重要,第一个句点在我的数据集上总是更好。

这只是一个简单的例子,但有时它涉及两个以上的间隔甚至一些逻辑(例如比较isoweek / weekday而不是月/日,比较不同的商店等)。

虽然我已经找到了几种方法来实现同样的目标,但我想知道是否有一种聪明的方法来实现同样的目标。也许更清洁的解决方案,或更适合大数据集(超过TB)。

例如,我认为第二个对大数据集的资源密集程度较低,因为它对表进行单个索引扫描。另一方面,第一个需要两个Index Scans和一个Merge。如果桌子太大而不适合记忆,会发生什么?或者第一个总是更好?

2 个答案:

答案 0 :(得分:0)

很少有这种做事方式总是更好,特别是当他们做非常相似的事情时。

然而,我建议您尝试尽可能利用最佳实践,例如尽量减少查询中标量函数的使用,因为这会抑制索引的使用。

例如,通过将您的第二个查询更改为以下内容,我认为您至少会看到一些改进性能:

SELECT
    Sales.StoreId
    , SUM(CASE WHEN Date BETWEEN '2017-09-01' AND '2017-09-30' THEN Revenue ELSE 0 END) RevenueThisYear
    , SUM(CASE WHEN Date BETWEEN '2016-09-01' AND '2016-09-30' THEN Revenue ELSE 0 END) RevenueLastYear
FROM
    Sales
WHERE
    Date BETWEEN '2017-09-01' AND '2017-09-30' 
    or Date BETWEEN '2016-09-01' AND '2016-09-30'
GROUP BY
    StoreId

答案 1 :(得分:0)

第二个看起来更好。但我猜年份部分正在减慢查询速度。让我们把这一年拿出去。 2017-01-01将在今年范围内更大(' 2017-09-01' AND' 2017-09-30')并且去年范围更少(' 2016-09-01' AND' 2016-09-30')。

 SELECT
        Sales.StoreId
        , SUM(CASE WHEN date > 2017-01-01 THEN Revenue ELSE 0 END) RevenueThisYear
        , SUM(CASE WHEN date < 2017-01-01 THEN Revenue ELSE 0 END) RevenueLastYear
    FROM
        Sales
    WHERE
        Date BETWEEN '2017-09-01' AND '2017-09-30' 
        or Date BETWEEN '2016-09-01' AND '2016-09-30'
    GROUP BY
        StoreId

如果完全加入效果很好,那就试试吧。

SELECT
    COALESCE(SalesThisYear.StoreId, SalesLastYear.StoreId) StoreId
    , sum(SalesThisYear.Revenue) RevenueThisYear
    , sum(SalesLastYear.Revenue) RevenueLastYear
  FROM     Sales SalesThisYear full join
           Sales SalesLastYear
    ON SalesLastYear.StoreId = SalesThisYear.StoreId
 WHERE    SalesThisYear.Date BETWEEN '2017-09-01' AND '2017-09-30'
   AND    SalesLastYear.Date BETWEEN '2016-09-01' AND '2016-09-30'
GROUP BY COALESCE(SalesThisYear.StoreId, SalesLastYear.StoreId)

修改*

  SELECT  Sales.StoreId
        , SUM(CASE WHEN date > '2017-01-01' THEN Revenue ELSE 0 END) RevenueThisYear
        , SUM(CASE WHEN date < '2017-01-01' THEN Revenue ELSE 0 END) RevenueLastYear
    FROM
        (Select store_id, date, revenue 
           from Sales
           WHERE Date BETWEEN '2017-09-01' AND '2017-09-30' 
              or Date BETWEEN '2016-09-01' AND '2016-09-30') q

GROUP BY StoreId