Atualização , se você estiver executando o SQL Server 2012, consulte: https://stackoverflow.com/a/10309947
O problema é que a implementação do SQL Server da cláusula Over é um pouco limitada .
Oracle (e ANSI-SQL) permitem que você faça coisas como:
SELECT somedate, somevalue,
SUM(somevalue) OVER(ORDER BY somedate
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
AS RunningTotal
FROM Table
O SQL Server não fornece uma solução limpa para esse problema. Meu instinto está me dizendo que esse é um daqueles casos raros em que um cursor é o mais rápido, embora eu tenha que fazer alguns testes comparativos de grandes resultados.
O truque de atualização é útil, mas eu sinto que é bastante frágil. Parece que se você estiver atualizando uma tabela completa, ela prosseguirá na ordem da chave primária. Portanto, se você definir sua data como uma chave primária ascendente, probably
estará seguro. Mas você está confiando em um detalhe de implementação não documentado do SQL Server (também se a consulta acabar sendo executada por dois procs, imagino o que acontecerá, consulte: MAXDOP):
Amostra de trabalho 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
Você pediu um benchmark, este é o ponto inicial.
A maneira mais rápida e segura de fazer isso seria o Cursor, é uma ordem de magnitude mais rápida que a subconsulta correlacionada da junção cruzada.
O caminho mais rápido é o truque UPDATE. Minha única preocupação é que não tenho certeza de que, em todas as circunstâncias, a atualização ocorrerá de maneira linear. Não há nada na consulta que diga isso explicitamente.
Bottom line, para o código de produção eu iria com o cursor.
Dados de teste:
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
Teste 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
Teste 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
Teste 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
Teste 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