sql >> Base de Datos >  >> RDS >> Sqlserver

Calcular un total acumulado en SQL Server

Actualizar , si está ejecutando SQL Server 2012, consulte:https://stackoverflow.com/a/10309947

El problema es que la implementación de SQL Server de la cláusula Over es algo limitada.

Oracle (y ANSI-SQL) le permiten hacer cosas como:

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

SQL Server no le brinda una solución limpia a este problema. Mi instinto me dice que este es uno de esos raros casos en los que un cursor es el más rápido, aunque tendré que hacer algunas evaluaciones comparativas de los grandes resultados.

El truco de actualización es útil, pero siento que es bastante frágil. Parece que si está actualizando una tabla completa, procederá en el orden de la clave principal. Entonces, si configura su fecha como clave principal ascendente, probably estar a salvo. Pero confía en un detalle de implementación de SQL Server no documentado (también si la consulta termina siendo realizada por dos procesos, me pregunto qué sucederá, consulte:MAXDOP):

Muestra de trabajo completa:

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

Usted pidió un punto de referencia, este es el detalle.

La forma SEGURA más rápida de hacer esto sería el Cursor, es un orden de magnitud más rápido que la subconsulta correlacionada de unión cruzada.

La forma más rápida absoluta es el truco ACTUALIZAR. Mi única preocupación es que no estoy seguro de que, en todas las circunstancias, la actualización se realice de forma lineal. No hay nada en la consulta que lo diga explícitamente.

En pocas palabras, para el código de producción iría con el cursor.

Datos de prueba:

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

Prueba 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 

Prueba 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

Prueba 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

Prueba 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