【问题标题】:Partitioning results in a running totals query分区导致运行总计查询
【发布时间】:2012-06-05 14:52:30
【问题描述】:

我正在寻找一种在大型 SQL Server 2008 数据集中创建累积总计的快速方法,该数据集按特定列进行分区,可能使用多重赋值变量解决方案。作为一个非常基本的示例,我想在下面创建“cumulative_total”列:

user_id | month | total | cumulative_total

1       | 1     | 2.0   | 2.0
1       | 2     | 1.0   | 3.0
1       | 3     | 3.5   | 8.5

2       | 1     | 0.5   | 0.5
2       | 2     | 1.5   | 2.0
2       | 3     | 2.0   | 4.0

传统上,我们使用相关子查询来完成此操作,但在处理大量数据(超过 200,000 行和几个不同类别的运行总数)时,这并不能为我们提供理想的性能。

我最近在这里读到了关于使用多个赋值变量进行累积求和:

http://sqlblog.com/blogs/paul_nielsen/archive/2007/12/06/cumulative-totals-screencast.aspx

在该博客的示例中,累积变量解决方案如下所示:

UPDATE my_table
SET @CumulativeTotal=cumulative_total=@CumulativeTotal+ISNULL(total, 0)

对于上述示例中的单个用户(用户 1 或用户 2)求和,此解决方案似乎非常快。但是,我需要按用户有效分区 - 给我按月按用户的累计总数。

有没有人知道扩展多重赋值变量概念来解决这个问题的方法,或者除了相关子查询或游标之外的任何其他想法吗?

非常感谢任何提示。

【问题讨论】:

  • 您是否拥有大量用户或大量月份或两者兼而有之?还有什么版本的 SQL Server?
  • 嗨亚伦。大量的用户,但只有几个月(从不超过大约 24 个)。 SQL Server 2008。
  • 我认为你的第三行应该有cumulative_total = 6.5,而不是8.5

标签: sql-server sql-server-2008


【解决方案1】:

您在 SQL Server 2008 中的选择相当有限 - 您可以根据上述方法执行某些操作(称为“古怪更新”),也可以在 CLR 中执行某些操作。

我个人会选择 CLR,因为它可以保证工作,而古怪的更新语法不是正式支持的东西(因此可能会在未来的版本中中断)。

您正在寻找的古怪更新语法的变化类似于:

UPDATE my_table
SET @CumulativeTotal=cumulative_total=ISNULL(total, 0) + 
        CASE WHEN @user=@lastUser THEN @CumulativeTotal ELSE 0 END, 
    @user=lastUser

值得注意的是,在 SQL Server 2012 中引入了对窗口函数的 RANGE 支持,因此这可以以最有效的方式表达,同时得到 100% 的支持。

【讨论】:

  • 太好了,太完美了——谢谢马特。对支持的担忧以及关于 RANGE 的注释已被记录和赞赏。
  • @Matt 正如我在答案末尾指出的那样,如果您已经为此使用RANGE,您应该测试ROWS 是否产生相同的结果(它不在所有情况),因为它应该更有效。另外,您是否知道任何已发布的 CLR 解决方案比此页面上的方法更快?我知道 CLR 对某些事情(例如拆分字符串)非常有用,但特别是什么使它更好地解决了这个问题?稍微快一点的数学计算?我有点期望 CLR 开销超过那里的任何好处(但很高兴被证明是错误的)。
  • @AaronBertrand - 是的,我应该明确地说 RANGE/ROWS,因为我倾向于在一个屋檐下查看整个语法扩展。我所说的 CLR 位是在线轴上进行计算,它非常擅长。我以前用 CLR 做这种事情的结果非常好。尝试对其进行一些良好的测量将是一个有趣的练习。
  • @Matt 我实际上是did some testing on this 和 CLR 方法可与新的 SQL Server 2012 方法相媲美(所有其他方法的性能都更差)。不过,游标的性能甚至胜过古怪的更新。
  • @AaronBertrand - 干得好。不要误会我的意思,我不会推荐古怪的更新 - 只是试图回答所提出的问题。
【解决方案2】:

如果您不需要存储数据(您不应该这样做,因为您需要在任何行发生更改、添加或删除时更新运行总计),并且如果您不信任古怪的更新(您不应该这样做,因为它不能保证工作,并且它的行为可能会随着修补程序、服务包、升级甚至基础索引或统计信息的更改而改变),您可以在运行时尝试这种类型的查询。这是 MVP Hugo Kornelis 创造的“基于集合的迭代”的方法(他在 SQL Server MVP Deep Dives 的一个章节中发布了类似的内容)。由于运行总计通常需要在整个集合上使用游标,对整个集合进行古怪的更新,或者随着行数的增加而变得越来越昂贵的单个非线性自连接,这里的技巧是循环一些有限的集合中的元素(在这种情况下,对于每个用户,每行的“排名”以月份为单位 - 并且您只为该排名的所有用户/月份组合处理每个排名一次,因此不是循环遍历 200,000 行,最多循环 24 次)。

DECLARE @t TABLE
(
  [user_id] INT, 
  [month] TINYINT,
  total DECIMAL(10,1), 
  RunningTotal DECIMAL(10,1), 
  Rnk INT
);

INSERT @t SELECT [user_id], [month], total, total, 
  RANK() OVER (PARTITION BY [user_id] ORDER BY [month]) 
  FROM dbo.my_table;

DECLARE @rnk INT = 1, @rc INT = 1;

WHILE @rc > 0
BEGIN
  SET @rnk += 1;

  UPDATE c SET RunningTotal = p.RunningTotal + c.total
    FROM @t AS c INNER JOIN @t AS p
    ON c.[user_id] = p.[user_id]
    AND p.rnk = @rnk - 1
    AND c.rnk = @rnk;

  SET @rc = @@ROWCOUNT;
END

SELECT [user_id], [month], total, RunningTotal
FROM @t
ORDER BY [user_id], rnk;

结果:

user_id  month   total   RunningTotal
-------  -----   -----   ------------
1        1       2.0     2.0
1        2       1.0     3.0
1        3       3.5     6.5 -- I think your calculation is off
2        1       0.5     0.5
2        2       1.5     2.0
2        3       2.0     4.0

当然你可以从这个表变量更新基表,但是为什么要麻烦,因为这些存储的值只有在下次任何 DML 语句触及表时才有效?

UPDATE mt
  SET cumulative_total = t.RunningTotal
  FROM dbo.my_table AS mt
  INNER JOIN @t AS t
  ON mt.[user_id] = t.[user_id]
  AND mt.[month] = t.[month];

由于我们不依赖任何类型的隐式排序,因此这是 100% 支持的,并且值得与不受支持的古怪更新进行性能比较。即使它没有击败它但接近它,您也应该考虑使用它恕我直言。

关于 SQL Server 2012 解决方案,Matt 提到了RANGE,但由于此方法使用磁盘假脱机,因此您还应该使用ROWS 进行测试,而不是仅使用RANGE 运行。这是您的案例的一个简单示例:

SELECT
  [user_id],
  [month],
  total,
  RunningTotal = SUM(total) OVER 
  (
    PARTITION BY [user_id] 
    ORDER BY [month] ROWS UNBOUNDED PRECEDING
  )
FROM dbo.my_table
ORDER BY [user_id], [month];

将此与RANGE UNBOUNDED PRECEDING 或根本没有ROWS\RANGE 进行比较(这也将使用RANGE 磁盘假脱机)。尽管计划看起来稍微复杂一些(额外的序列项目操作员),但上述计划的总体持续时间会更短,并且 I/O 会更少方式

我最近发表了一篇博文,概述了我在特定运行总计场景中观察到的一些性能差异:

http://www.sqlperformance.com/2012/07/t-sql-queries/running-totals

【讨论】:

  • 谢谢亚伦,这很有帮助。非常感谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-10-23
  • 2015-12-18
  • 2015-12-20
  • 1970-01-01
  • 2019-03-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多