如果您不需要存储数据(您不应该这样做,因为您需要在任何行更改、添加或删除时更新运行总计),并且如果您不信任古怪的更新(您不应该,因为它不能保证工作,并且它的行为可能会随着修补程序、服务包、升级甚至基础索引或统计信息的变化而改变),您可以在运行时尝试这种类型的查询。这是 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