Pokud nepotřebujete UCHOVÁVAT data (což byste neměli, protože potřebujete aktualizovat průběžné součty při každé změně, přidání nebo odstranění řádku), a pokud nedůvěřujete svérázné aktualizaci (které by nemělo, protože není zaručeno, že bude fungovat a jeho chování by se mohlo změnit opravou hotfix, aktualizací Service Pack, upgradem nebo dokonce změnou základního indexu nebo statistik), můžete tento typ dotazu vyzkoušet za běhu. Toto je metoda, kterou kolega MVP Hugo Kornelis vytvořil „set-based iteration“ (něco podobného zveřejnil v jedné ze svých kapitol SQL Server MVP Deep Dives ). Vzhledem k tomu, že spouštění součtů obvykle vyžaduje kurzor nad celou sadou, svéráznou aktualizaci nad celou sadou nebo jediné nelineární samospojení, které se s rostoucím počtem řádků stává stále dražším, je zde trik procházet nějakým konečným prvek v sadě (v tomto případě „hodnocení“ každého řádku z hlediska měsíce, pro každého uživatele – a vy zpracujete každé hodnocení pouze jednou pro všechny kombinace uživatel/měsíc v tomto pořadí, takže místo procházení 200 000 řádky, opakujte až 24krát).
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;
Výsledky:
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
Samozřejmě můžete aktualizujte základní tabulku z této proměnné tabulky, ale proč se obtěžovat, protože tyto uložené hodnoty jsou dobré pouze do doby, než se tabulky příště dotkne jakýkoli příkaz 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];
Protože se nespoléháme na implicitní řazení jakéhokoli druhu, je to 100% podporováno a zaslouží si srovnání výkonu ve srovnání s nepodporovanou nepředvídatelnou aktualizací. I když to nepřekoná, ale blíží se, měli byste zvážit jeho použití IMHO.
Pokud jde o řešení SQL Server 2012, Matt zmiňuje RANGE
ale protože tato metoda používá zařazování na disku, měli byste také testovat pomocí ROWS
místo pouhého spuštění s RANGE
. Zde je rychlý příklad pro váš případ:
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];
Porovnejte to s RANGE UNBOUNDED PRECEDING
nebo žádný ROWS\RANGE
vůbec (což bude také používat RANGE
cívka na disku). Výše uvedené bude mít nižší celkovou dobu trvání a způsob méně I/O, i když plán vypadá o něco složitější (další operátor sekvenčního projektu).
Nedávno jsem publikoval příspěvek na blogu popisující některé rozdíly ve výkonu, které jsem pozoroval u konkrétního scénáře průběžných součtů:
http://www.sqlperformance.com/2012/07 /t-sql-queries/running-totals