En bra resurs för att beräkna löpande totaler i SQL Server är det här dokumentet
av Itzik Ben Gan som skickades till SQL Server Team som en del av hans kampanj för att få OVER
klausul utvidgades längre från dess initiala SQL Server 2005-implementering. I den visar han hur när du väl kommer in i tiotusentals rader, så utför markörerna uppsättningsbaserade lösningar. SQL Server 2012 utökade verkligen OVER
klausul som gör den här typen av frågor mycket enklare.
SELECT col1,
SUM(col1) OVER (ORDER BY ind ROWS UNBOUNDED PRECEDING)
FROM @tmp
Eftersom du använder SQL Server 2005 är detta dock inte tillgängligt för dig.
Adam Machanic visar här hur CLR kan användas för att förbättra prestandan för standard TSQL-markörer.
För denna tabelldefinition
CREATE TABLE RunningTotals
(
ind int identity(1,1) primary key,
col1 int
)
Jag skapar tabeller med både 2 000 och 10 000 rader i en databas med ALLOW_SNAPSHOT_ISOLATION PÅ
och en med den här inställningen av (Anledningen till detta är att mina initiala resultat var i en DB med inställningen på som ledde till en förbryllande aspekt av resultaten).
De klustrade indexen för alla tabeller hade bara 1 rotsida. Antalet bladsidor för varje visas nedan.
+-------------------------------+-----------+------------+
| | 2,000 row | 10,000 row |
+-------------------------------+-----------+------------+
| ALLOW_SNAPSHOT_ISOLATION OFF | 5 | 22 |
| ALLOW_SNAPSHOT_ISOLATION ON | 8 | 39 |
+-------------------------------+-----------+------------+
Jag testade följande fall (länkar visar genomförandeplaner)
- Vänster Gå med och Gruppera av
- Korrelerad underfråga 2000 radplan ,10 000 rader
- CTE från Mikaels (uppdaterade) svar
- CTE nedan
Anledningen till att det ytterligare CTE-alternativet inkluderades var för att tillhandahålla en CTE-lösning som fortfarande skulle fungera om ind
kolumnen var inte garanterad sekventiell.
SET STATISTICS IO ON;
SET STATISTICS TIME ON;
DECLARE @col1 int, @sumcol1 bigint;
WITH RecursiveCTE
AS (
SELECT TOP 1 ind, col1, CAST(col1 AS BIGINT) AS Total
FROM RunningTotals
ORDER BY ind
UNION ALL
SELECT R.ind, R.col1, R.Total
FROM (
SELECT T.*,
T.col1 + Total AS Total,
rn = ROW_NUMBER() OVER (ORDER BY T.ind)
FROM RunningTotals T
JOIN RecursiveCTE R
ON R.ind < T.ind
) R
WHERE R.rn = 1
)
SELECT @col1 =col1, @sumcol1=Total
FROM RecursiveCTE
OPTION (MAXRECURSION 0);
Alla frågorna hade en CAST(col1 AS BIGINT)
läggs till för att undvika spillfel vid körning. Dessutom tilldelade jag resultaten för dem alla till variabler enligt ovan för att eliminera tid som går åt till att skicka tillbaka resultat från övervägande.
Resultat
+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
| | | | Base Table | Work Table | Time |
+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
| | Snapshot | Rows | Scan count | logical reads | Scan count | logical reads | cpu | elapsed |
| Group By | On | 2,000 | 2001 | 12709 | | | 1469 | 1250 |
| | On | 10,000 | 10001 | 216678 | | | 30906 | 30963 |
| | Off | 2,000 | 2001 | 9251 | | | 1140 | 1160 |
| | Off | 10,000 | 10001 | 130089 | | | 29906 | 28306 |
+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
| Sub Query | On | 2,000 | 2001 | 12709 | | | 844 | 823 |
| | On | 10,000 | 2 | 82 | 10000 | 165025 | 24672 | 24535 |
| | Off | 2,000 | 2001 | 9251 | | | 766 | 999 |
| | Off | 10,000 | 2 | 48 | 10000 | 165025 | 25188 | 23880 |
+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
| CTE No Gaps | On | 2,000 | 0 | 4002 | 2 | 12001 | 78 | 101 |
| | On | 10,000 | 0 | 20002 | 2 | 60001 | 344 | 342 |
| | Off | 2,000 | 0 | 4002 | 2 | 12001 | 62 | 253 |
| | Off | 10,000 | 0 | 20002 | 2 | 60001 | 281 | 326 |
+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
| CTE Alllows Gaps | On | 2,000 | 2001 | 4009 | 2 | 12001 | 47 | 75 |
| | On | 10,000 | 10001 | 20040 | 2 | 60001 | 312 | 413 |
| | Off | 2,000 | 2001 | 4006 | 2 | 12001 | 94 | 90 |
| | Off | 10,000 | 10001 | 20023 | 2 | 60001 | 313 | 349 |
+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
Både den korrelerade underfrågan och GROUP BY
version använder "triangulära" kapslade loop-kopplingar som drivs av en klustrad indexskanning på RunningTotals
tabell (T1
) och, för varje rad som returneras av den skanningen, söka tillbaka till tabellen (T2
) går själv med på T2.ind<=T1.ind
.
Detta innebär att samma rader bearbetas upprepade gånger. När T1.ind=1000
rad bearbetas självanslutningen hämtar och summerar alla rader med en ind <=1000
, sedan för nästa rad där T1.ind=1001
samma 1000 rader hämtas igen och summeras tillsammans med ytterligare en rad och så vidare.
Det totala antalet sådana operationer för en tabell med 2 000 rader är 2 001 000, för 10 000 rader 50 005 000 eller mer generellt (n² + n) / 2 som tydligt växer exponentiellt.
I fallet med 2 000 rader är den största skillnaden mellan GROUP BY
och versionerna av underfrågan är att den förra har flödesaggregatet efter sammanfogningen och så har tre kolumner som matas in i den (T1.ind
, T2.col1
, T2.col1
) och en GROUP BY
egenskapen för T1.ind
medan den senare beräknas som ett skalärt aggregat, med strömaggregatet före sammanfogningen, endast har T2.col1
matas in i den och har ingen GROUP BY
egendomsuppsättning alls. Detta enklare arrangemang kan ses ha en mätbar fördel i form av minskad CPU-tid.
För fallet med 10 000 rader finns det en ytterligare skillnad i underfrågeplanen. Den lägger till en ivrig spole
som kopierar alla ind,cast(col1 som bigint)
värden till tempdb
. I det fall att ögonblicksbildsisolering är på detta fungerar det mer kompakt än den klustrade indexstrukturen och nettoeffekten är att minska antalet läsningar med cirka 25 % (eftersom bastabellen bevarar ganska mycket tomt utrymme för versionsinformation), när det här alternativet är avstängt fungerar det mindre kompakt (förmodligen på grund av bigint
). kontra int
skillnad) och resultatet av fler läsningar. Detta minskar gapet mellan underfrågan och grupp efter version, men underfrågan vinner fortfarande.
Den klara vinnaren var dock Recursive CTE. För versionen "inga luckor" är logiska läsningar från bastabellen nu 2 x (n + 1)
återspeglar n
index söker in i 2-nivåindexet för att hämta alla rader plus den ytterligare i slutet som inte returnerar något och avslutar rekursionen. Det innebar ändå 20 002 läsningar för att bearbeta en tabell på 22 sidor!
Logiska arbetstabellsavläsningar för den rekursiva CTE-versionen är mycket höga. Det verkar fungera med 6 arbetsbordsläsningar per källrad. Dessa kommer från indexspolen som lagrar utdata från föregående rad och läses sedan av igen i nästa iteration (bra förklaring av detta av Umachandar Jayachandran här ). Trots det höga antalet presterar detta fortfarande bäst.