Pro ty, kteří nepoužívají SQL Server 2012 nebo vyšší, je pravděpodobně nejúčinnějším podporovaným kurzor. a zaručené metoda mimo CLR. Existují i další přístupy, jako je „zvláštní aktualizace“, která může být o něco rychlejší, ale není zaručeno, že bude v budoucnu fungovat, a samozřejmě přístupy založené na množinách s hyperbolickými profily výkonu, jak se tabulka zvětšuje, a rekurzivní metody CTE, které často vyžadují přímé #tempdb I/O nebo způsobit úniky, které mají zhruba stejný dopad.
INNER JOIN – nedělejte toto:
Pomalý přístup založený na množinách má tvar:
SELECT t1.TID, t1.amt, RunningTotal = SUM(t2.amt)
FROM dbo.Transactions AS t1
INNER JOIN dbo.Transactions AS t2
ON t1.TID >= t2.TID
GROUP BY t1.TID, t1.amt
ORDER BY t1.TID;
Proč je to pomalé? Jak se tabulka zvětšuje, každý přírůstkový řádek vyžaduje čtení n-1 řádků v tabulce. To je exponenciální a vázáno na selhání, časové limity nebo jen naštvané uživatele.
Korelovaný poddotaz – neprovádějte ani toto:
Formulář dílčího dotazu je podobně bolestivý z podobně bolestivých důvodů.
SELECT TID, amt, RunningTotal = amt + COALESCE(
(
SELECT SUM(amt)
FROM dbo.Transactions AS i
WHERE i.TID < o.TID), 0
)
FROM dbo.Transactions AS o
ORDER BY TID;
Svérázná aktualizace – provádějte to na vlastní riziko:
Metoda „zvláštní aktualizace“ je efektivnější než výše uvedená, ale chování není zdokumentováno, neexistují žádné záruky ohledně pořádku a chování může fungovat dnes, ale v budoucnu se může zlomit. Zahrnuji to, protože je to populární metoda a je účinná, ale to neznamená, že ji podporuji. Hlavním důvodem, proč jsem na tuto otázku odpověděl, místo abych ji zavřel jako duplikát, je to, že druhá otázka má jako přijatou odpověď podivnou aktualizaci.
DECLARE @t TABLE
(
TID INT PRIMARY KEY,
amt INT,
RunningTotal INT
);
DECLARE @RunningTotal INT = 0;
INSERT @t(TID, amt, RunningTotal)
SELECT TID, amt, RunningTotal = 0
FROM dbo.Transactions
ORDER BY TID;
UPDATE @t
SET @RunningTotal = RunningTotal = @RunningTotal + amt
FROM @t;
SELECT TID, amt, RunningTotal
FROM @t
ORDER BY TID;
Rekurzivní CTE
Tento první spoléhá na to, že TID bude souvislý, bez mezer:
;WITH x AS
(
SELECT TID, amt, RunningTotal = amt
FROM dbo.Transactions
WHERE TID = 1
UNION ALL
SELECT y.TID, y.amt, x.RunningTotal + y.amt
FROM x
INNER JOIN dbo.Transactions AS y
ON y.TID = x.TID + 1
)
SELECT TID, amt, RunningTotal
FROM x
ORDER BY TID
OPTION (MAXRECURSION 10000);
Pokud se na to nemůžete spolehnout, můžete použít tuto variantu, která jednoduše vytvoří souvislou sekvenci pomocí ROW_NUMBER()
:
;WITH y AS
(
SELECT TID, amt, rn = ROW_NUMBER() OVER (ORDER BY TID)
FROM dbo.Transactions
), x AS
(
SELECT TID, rn, amt, rt = amt
FROM y
WHERE rn = 1
UNION ALL
SELECT y.TID, y.rn, y.amt, x.rt + y.amt
FROM x INNER JOIN y
ON y.rn = x.rn + 1
)
SELECT TID, amt, RunningTotal = rt
FROM x
ORDER BY x.rn
OPTION (MAXRECURSION 10000);
V závislosti na velikosti dat (např. sloupců, o kterých nevíme), můžete dosáhnout lepšího celkového výkonu tím, že příslušné sloupce nejprve nacpete pouze do #temp tabulky a zpracujete proti ní namísto základní tabulky:
CREATE TABLE #x
(
rn INT PRIMARY KEY,
TID INT,
amt INT
);
INSERT INTO #x (rn, TID, amt)
SELECT ROW_NUMBER() OVER (ORDER BY TID),
TID, amt
FROM dbo.Transactions;
;WITH x AS
(
SELECT TID, rn, amt, rt = amt
FROM #x
WHERE rn = 1
UNION ALL
SELECT y.TID, y.rn, y.amt, x.rt + y.amt
FROM x INNER JOIN #x AS y
ON y.rn = x.rn + 1
)
SELECT TID, amt, RunningTotal = rt
FROM x
ORDER BY TID
OPTION (MAXRECURSION 10000);
DROP TABLE #x;
Pouze první metoda CTE poskytne výkon konkurující svérázné aktualizaci, ale vytváří velký předpoklad o povaze dat (žádné mezery). Další dvě metody se vrátí zpět a v těchto případech můžete také použít kurzor (pokud nemůžete použít CLR a ještě nepoužíváte SQL Server 2012 nebo vyšší).
Kurzor
Každému se říká, že kurzory jsou zlé a že je třeba se jim za každou cenu vyhnout, ale to ve skutečnosti překonává výkon většiny ostatních podporovaných metod a je to bezpečnější než podivná aktualizace. Jediné, co preferuji před řešením kurzoru, jsou metody 2012 a CLR (níže):
CREATE TABLE #x
(
TID INT PRIMARY KEY,
amt INT,
rt INT
);
INSERT #x(TID, amt)
SELECT TID, amt
FROM dbo.Transactions
ORDER BY TID;
DECLARE @rt INT, @tid INT, @amt INT;
SET @rt = 0;
DECLARE c CURSOR LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR SELECT TID, amt FROM #x ORDER BY TID;
OPEN c;
FETCH c INTO @tid, @amt;
WHILE @@FETCH_STATUS = 0
BEGIN
SET @rt = @rt + @amt;
UPDATE #x SET rt = @rt WHERE TID = @tid;
FETCH c INTO @tid, @amt;
END
CLOSE c; DEALLOCATE c;
SELECT TID, amt, RunningTotal = rt
FROM #x
ORDER BY TID;
DROP TABLE #x;
SQL Server 2012 nebo vyšší
Nové funkce okna zavedené v SQL Server 2012 tento úkol značně usnadňují (a také funguje lépe než všechny výše uvedené metody):
SELECT TID, amt,
RunningTotal = SUM(amt) OVER (ORDER BY TID ROWS UNBOUNDED PRECEDING)
FROM dbo.Transactions
ORDER BY TID;
Všimněte si, že na větších souborech dat zjistíte, že výše uvedené funguje mnohem lépe než kterákoli z následujících dvou možností, protože RANGE používá zařazování na disku (a výchozí používá RANGE). Je však také důležité poznamenat, že chování a výsledky se mohou lišit, takže se ujistěte, že oba vrátí správné výsledky, než se mezi nimi na základě tohoto rozdílu rozhodnete.
SELECT TID, amt,
RunningTotal = SUM(amt) OVER (ORDER BY TID)
FROM dbo.Transactions
ORDER BY TID;
SELECT TID, amt,
RunningTotal = SUM(amt) OVER (ORDER BY TID RANGE UNBOUNDED PRECEDING)
FROM dbo.Transactions
ORDER BY TID;
CLR
Pro úplnost nabízím odkaz na metodu CLR Pavla Pawlowského, což je zdaleka preferovaná metoda ve verzích před SQL Server 2012 (ale samozřejmě ne 2000).
http://www.pawlowski.cz/2010/09/sql-server-and-fastest-running-totals-using-clr/
Závěr
Pokud používáte SQL Server 2012 nebo vyšší, volba je zřejmá – použijte nový SUM() OVER()
konstrukci (pomocí ROWS
vs. RANGE
). U dřívějších verzí budete chtít porovnat výkon alternativních přístupů na vašem schématu, datech a – s ohledem na faktory nesouvisející s výkonem – určit, který přístup je pro vás ten pravý. Velmi dobře to může být přístup CLR. Zde jsou má doporučení v pořadí podle preferencí:
SUM() OVER() ... ROWS
, pokud jde o rok 2012 nebo vyšší- Metoda CLR, pokud je to možné
- Pokud je to možné, první rekurzivní metoda CTE
- Kurzor
- Další rekurzivní metody CTE
- Nevšední aktualizace
- Připojit se a/nebo korelovat dílčí dotaz
Další informace s porovnáním výkonu těchto metod naleznete v této otázce na http://dba.stackexchange.com:
https://dba.stackexchange.com/questions/19507/running-total-with-count
Více podrobností o těchto srovnáních jsem také napsal zde:
http://www.sqlperformance.com/2012/07/t-sql-queries/running-totals
Také pro seskupené/rozdělené průběžné součty viz následující příspěvky:
http://sqlperformance.com/2014/01/t-sql-queries/grouped-running-totals
Výsledkem rozdělení je dotaz na součet
Více průběžných součtů s Group By