Ако не е необходимо да СЪХРАНЯВАТЕ данните (което не трябва, защото трябва да актуализирате текущите суми всеки път, когато някой ред се промени, добави или изтрие) и ако не вярвате на странната актуализация (която вие не трябва, тъй като не е гарантирано, че ще работи и поведението му може да се промени с актуална корекция, сервизен пакет, надстройка или дори промяна на основен индекс или статистика), можете да опитате този тип заявка по време на изпълнение. Това е метод, който колегата MVP Hugo Kornelis измисли „базирана на набор итерация“ (той публикува нещо подобно в една от главите си на Дълбоки гмуркания на SQL Server MVP ). Тъй като текущите суми обикновено изискват курсор върху целия набор, странна актуализация върху целия набор или едно нелинейно самообединяване, което става все по-скъпо с увеличаване на броя на редовете, трикът тук е да преминете през някакъв краен елемент в набора (в този случай, "рангът" на всеки ред по отношение на месец, за всеки потребител - и вие обработвате само всеки ранг веднъж за всички комбинации потребител/месец в този ранг, така че вместо да преминавате през 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% и заслужава сравнение на производителността в сравнение с неподдържаната странна актуализация. Дори и да не го победи, но да се доближи, все пак трябва да помислите да го използвате, IMHO.
Що се отнася до решението на 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