Als u de gegevens niet OPSLAAN (wat u niet zou moeten doen, omdat u de lopende totalen moet bijwerken telkens wanneer een rij wordt gewijzigd, toegevoegd of verwijderd), en als u de eigenaardige update (die u zou niet moeten, omdat het niet gegarandeerd werkt en het gedrag kan veranderen met een hotfix, servicepack, upgrade of zelfs een onderliggende index of statistische wijziging), kunt u dit type query tijdens runtime proberen. Dit is een methode die collega MVP Hugo Kornelis "set-based iteratie" bedacht (hij plaatste iets soortgelijks in een van zijn hoofdstukken van SQL Server MVP diepe duiken ). Aangezien het uitvoeren van totalen doorgaans een cursor over de hele set vereist, een eigenzinnige update over de hele set, of een enkele niet-lineaire self-join die steeds duurder wordt naarmate het aantal rijen toeneemt, is de truc hier om door een eindige element in de set (in dit geval de "rang" van elke rij in termen van maand, voor elke gebruiker - en je verwerkt elke rang slechts één keer voor alle combinaties van gebruiker/maand op die rang, dus in plaats van 200.000 rijen te doorlopen, u kunt maximaal 24 keer herhalen).
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;
Resultaten:
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
Natuurlijk kunt u update de basistabel van deze tabelvariabele, maar waarom zou u zich druk maken, aangezien die opgeslagen waarden alleen goed zijn tot de volgende keer dat de tabel wordt aangeraakt door een DML-instructie?
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];
Aangezien we niet vertrouwen op impliciete bestellingen van welke aard dan ook, wordt dit 100% ondersteund en verdient het een prestatievergelijking ten opzichte van de niet-ondersteunde eigenzinnige update. Zelfs als het niet beter is, maar in de buurt komt, zou je moeten overwegen het toch te gebruiken IMHO.
Wat betreft de SQL Server 2012-oplossing, noemt Matt RANGE
maar aangezien deze methode een spool op schijf gebruikt, moet u ook testen met ROWS
in plaats van alleen te draaien met RANGE
. Hier is een snel voorbeeld voor uw geval:
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];
Vergelijk dit met RANGE UNBOUNDED PRECEDING
of geen ROWS\RANGE
helemaal niet (die ook de RANGE
. zal gebruiken op de schijfspoel). Het bovenstaande heeft een lagere totale duur en manier minder I/O, hoewel het plan er iets complexer uitziet (een extra sequentieprojectoperator).
Ik heb onlangs een blogpost gepubliceerd waarin enkele prestatieverschillen worden beschreven die ik heb waargenomen voor een specifiek scenario met lopende totalen:
http://www.sqlperformance.com/2012/07 /t-sql-queries/lopende-totalen