Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Calculer un total cumulé dans SQL Server

Mettre à jour , si vous exécutez SQL Server 2012, voir :https://stackoverflow.com/a/10309947

Le problème est que l'implémentation SQL Server de la clause Over est quelque peu limitée.

Oracle (et ANSI-SQL) vous permettent de faire des choses comme :

 SELECT somedate, somevalue,
  SUM(somevalue) OVER(ORDER BY somedate 
     ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) 
          AS RunningTotal
  FROM Table

SQL Server ne vous donne aucune solution propre à ce problème. Mon instinct me dit que c'est l'un de ces rares cas où un curseur est le plus rapide, même si je devrai faire des analyses comparatives sur les gros résultats.

L'astuce de mise à jour est pratique mais je pense qu'elle est assez fragile. Il semble que si vous mettez à jour une table complète, cela se fera dans l'ordre de la clé primaire. Donc, si vous définissez votre date comme une clé primaire croissante, vous obtiendrez probably fais attention. Mais vous comptez sur un détail d'implémentation SQL Server non documenté (également si la requête finit par être effectuée par deux procs, je me demande ce qui se passera, voir :MAXDOP) :

Exemple de travail complet :

drop table #t 
create table #t ( ord int primary key, total int, running_total int)

insert #t(ord,total)  values (2,20)
-- notice the malicious re-ordering 
insert #t(ord,total) values (1,10)
insert #t(ord,total)  values (3,10)
insert #t(ord,total)  values (4,1)

declare @total int 
set @total = 0
update #t set running_total = @total, @total = @total + total 

select * from #t
order by ord 

ord         total       running_total
----------- ----------- -------------
1           10          10
2           20          30
3           10          40
4           1           41

Vous avez demandé une référence, c'est la vérité.

Le moyen le plus rapide et SÛR de le faire serait le curseur, c'est un ordre de grandeur plus rapide que la sous-requête corrélée de la jointure croisée.

Le moyen le plus rapide est l'astuce UPDATE. Ma seule préoccupation à ce sujet est que je ne suis pas certain qu'en toutes circonstances la mise à jour se déroulera de manière linéaire. Rien dans la requête ne le dit explicitement.

En bout de ligne, pour le code de production, j'irais avec le curseur.

Données de test :

create table #t ( ord int primary key, total int, running_total int)

set nocount on 
declare @i int
set @i = 0 
begin tran
while @i < 10000
begin
   insert #t (ord, total) values (@i,  rand() * 100) 
    set @i = @i +1
end
commit

Essai 1 :

SELECT ord,total, 
    (SELECT SUM(total) 
        FROM #t b 
        WHERE b.ord <= a.ord) AS b 
FROM #t a

-- CPU 11731, Reads 154934, Duration 11135 

Essai 2 :

SELECT a.ord, a.total, SUM(b.total) AS RunningTotal 
FROM #t a CROSS JOIN #t b 
WHERE (b.ord <= a.ord) 
GROUP BY a.ord,a.total 
ORDER BY a.ord

-- CPU 16053, Reads 154935, Duration 4647

Essai 3 :

DECLARE @TotalTable table(ord int primary key, total int, running_total int)

DECLARE forward_cursor CURSOR FAST_FORWARD 
FOR 
SELECT ord, total
FROM #t 
ORDER BY ord


OPEN forward_cursor 

DECLARE @running_total int, 
    @ord int, 
    @total int
SET @running_total = 0

FETCH NEXT FROM forward_cursor INTO @ord, @total 
WHILE (@@FETCH_STATUS = 0)
BEGIN
     SET @running_total = @running_total + @total
     INSERT @TotalTable VALUES(@ord, @total, @running_total)
     FETCH NEXT FROM forward_cursor INTO @ord, @total 
END

CLOSE forward_cursor
DEALLOCATE forward_cursor

SELECT * FROM @TotalTable

-- CPU 359, Reads 30392, Duration 496

Essai 4 :

declare @total int 
set @total = 0
update #t set running_total = @total, @total = @total + total 

select * from #t

-- CPU 0, Reads 58, Duration 139