如果您不需要存储数据(您不应该这样做,因为您需要在任何时候更改,添加或删除任何行时更新运行总计),并且如果您不相信这个古怪的更新(你不应该这样做,因为它不能保证工作,并且它的行为可能会随着修补程序,服务包,升级甚至底层索引或统计信息的改变而改变),你可以在运行时尝试这种类型的查询。这是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的解决方案,马特提到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
你有大量的用户或大量个月或两者?也是什么版本的SQL Server? –
嗨亚伦。大量的用户,但只有几个月(从未超过约24)。 SQL Server 2008. –
我认为你的第三行应该有'cumulative_total = 6.5',而不是'8.5'。 –