如果您不需要存儲數據(您不應該這樣做,因爲您需要在任何時候更改,添加或刪除任何行時更新運行總計),並且如果您不相信這個古怪的更新(你不應該這樣做,因爲它不能保證工作,並且它的行爲可能會隨着修補程序,服務包,升級甚至底層索引或統計信息的改變而改變),你可以在運行時嘗試這種類型的查詢。這是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'。 –