Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Calcola un totale parziale in SQL Server

Aggiorna , se stai utilizzando SQL Server 2012, consulta:https://stackoverflow.com/a/10309947

Il problema è che l'implementazione della clausola Over in SQL Server è alquanto limitata.

Oracle (e ANSI-SQL) ti consentono di fare cose come:

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

SQL Server non offre una soluzione pulita a questo problema. Il mio istinto mi dice che questo è uno di quei rari casi in cui un cursore è il più veloce, anche se dovrò fare dei benchmark per ottenere grandi risultati.

Il trucco dell'aggiornamento è utile ma sento che è abbastanza fragile. Sembra che se stai aggiornando una tabella completa, procederà nell'ordine della chiave primaria. Quindi, se imposti la tua data come chiave primaria ascendente, probably stai attento. Ma ti affidi a un dettaglio di implementazione di SQL Server non documentato (anche se la query finisce per essere eseguita da due proc mi chiedo cosa accadrà, vedi:MAXDOP):

Campione funzionante completo:

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

Hai chiesto un benchmark, questo è il minimo.

Il modo SICURO più veloce per farlo sarebbe il cursore, è un ordine di grandezza più veloce della sottoquery correlata di cross-join.

Il modo più veloce in assoluto è il trucco UPDATE. La mia unica preoccupazione è che non sono certo che in tutte le circostanze l'aggiornamento procederà in modo lineare. Non c'è nulla nella query che lo dice esplicitamente.

In conclusione, per il codice di produzione andrei con il cursore.

Dati di prova:

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

Prova 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 

Prova 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

Prova 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

Prova 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