Para aqueles que não usam o SQL Server 2012 ou superior, um cursor é provavelmente o suportado mais eficiente e garantido método fora do CLR. Existem outras abordagens, como a "atualização peculiar", que pode ser marginalmente mais rápida, mas não garantida para funcionar no futuro, e, claro, abordagens baseadas em conjuntos com perfis de desempenho hiperbólicos à medida que a tabela fica maior e métodos CTE recursivos que geralmente exigem #tempdb E/S ou resultar em derramamentos que produzem aproximadamente o mesmo impacto.
INNER JOIN - não faça isso:
A abordagem lenta e baseada em conjuntos é da forma:
SELECT t1.TID, t1.amt, RunningTotal = SUM(t2.amt)
FROM dbo.Transactions AS t1
INNER JOIN dbo.Transactions AS t2
ON t1.TID >= t2.TID
GROUP BY t1.TID, t1.amt
ORDER BY t1.TID;
A razão pela qual isso é lento? À medida que a tabela aumenta, cada linha incremental requer a leitura de n-1 linhas na tabela. Isso é exponencial e destinado a falhas, tempos limite ou apenas usuários irritados.
Subconsulta correlacionada - também não faça isso:
O formulário de subconsulta é igualmente doloroso por razões igualmente dolorosas.
SELECT TID, amt, RunningTotal = amt + COALESCE(
(
SELECT SUM(amt)
FROM dbo.Transactions AS i
WHERE i.TID < o.TID), 0
)
FROM dbo.Transactions AS o
ORDER BY TID;
Atualização peculiar - faça isso por sua conta e risco:
O método de "atualização peculiar" é mais eficiente que o acima, mas o comportamento não está documentado, não há garantias sobre a ordem e o comportamento pode funcionar hoje, mas pode falhar no futuro. Estou incluindo isso porque é um método popular e eficiente, mas isso não significa que eu o endosse. A principal razão pela qual eu respondi essa pergunta em vez de fechá-la como uma duplicata é porque a outra pergunta tem uma atualização peculiar como a resposta aceita.
DECLARE @t TABLE
(
TID INT PRIMARY KEY,
amt INT,
RunningTotal INT
);
DECLARE @RunningTotal INT = 0;
INSERT @t(TID, amt, RunningTotal)
SELECT TID, amt, RunningTotal = 0
FROM dbo.Transactions
ORDER BY TID;
UPDATE @t
SET @RunningTotal = RunningTotal = @RunningTotal + amt
FROM @t;
SELECT TID, amt, RunningTotal
FROM @t
ORDER BY TID;
CTEs recursivos
Este primeiro depende do TID para ser contíguo, sem lacunas:
;WITH x AS
(
SELECT TID, amt, RunningTotal = amt
FROM dbo.Transactions
WHERE TID = 1
UNION ALL
SELECT y.TID, y.amt, x.RunningTotal + y.amt
FROM x
INNER JOIN dbo.Transactions AS y
ON y.TID = x.TID + 1
)
SELECT TID, amt, RunningTotal
FROM x
ORDER BY TID
OPTION (MAXRECURSION 10000);
Se você não pode confiar nisso, então você pode usar esta variação, que simplesmente cria uma sequência contígua usando
ROW_NUMBER()
:;WITH y AS
(
SELECT TID, amt, rn = ROW_NUMBER() OVER (ORDER BY TID)
FROM dbo.Transactions
), x AS
(
SELECT TID, rn, amt, rt = amt
FROM y
WHERE rn = 1
UNION ALL
SELECT y.TID, y.rn, y.amt, x.rt + y.amt
FROM x INNER JOIN y
ON y.rn = x.rn + 1
)
SELECT TID, amt, RunningTotal = rt
FROM x
ORDER BY x.rn
OPTION (MAXRECURSION 10000);
Dependendo do tamanho dos dados (por exemplo, colunas que não conhecemos), você pode encontrar um desempenho geral melhor preenchendo as colunas relevantes apenas em uma tabela #temp primeiro e processando-a em vez da tabela base:
CREATE TABLE #x
(
rn INT PRIMARY KEY,
TID INT,
amt INT
);
INSERT INTO #x (rn, TID, amt)
SELECT ROW_NUMBER() OVER (ORDER BY TID),
TID, amt
FROM dbo.Transactions;
;WITH x AS
(
SELECT TID, rn, amt, rt = amt
FROM #x
WHERE rn = 1
UNION ALL
SELECT y.TID, y.rn, y.amt, x.rt + y.amt
FROM x INNER JOIN #x AS y
ON y.rn = x.rn + 1
)
SELECT TID, amt, RunningTotal = rt
FROM x
ORDER BY TID
OPTION (MAXRECURSION 10000);
DROP TABLE #x;
Apenas o primeiro método CTE fornecerá desempenho que rivaliza com a atualização peculiar, mas faz uma grande suposição sobre a natureza dos dados (sem lacunas). Os outros dois métodos retornarão e, nesses casos, você também poderá usar um cursor (se não puder usar o CLR e ainda não estiver no SQL Server 2012 ou superior).
Cursor
Todo mundo é informado de que os cursores são maus e que devem ser evitados a todo custo, mas isso realmente supera o desempenho da maioria dos outros métodos suportados e é mais seguro do que a atualização peculiar. Os únicos que prefiro sobre a solução do cursor são os métodos 2012 e CLR (abaixo):
CREATE TABLE #x
(
TID INT PRIMARY KEY,
amt INT,
rt INT
);
INSERT #x(TID, amt)
SELECT TID, amt
FROM dbo.Transactions
ORDER BY TID;
DECLARE @rt INT, @tid INT, @amt INT;
SET @rt = 0;
DECLARE c CURSOR LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR SELECT TID, amt FROM #x ORDER BY TID;
OPEN c;
FETCH c INTO @tid, @amt;
WHILE @@FETCH_STATUS = 0
BEGIN
SET @rt = @rt + @amt;
UPDATE #x SET rt = @rt WHERE TID = @tid;
FETCH c INTO @tid, @amt;
END
CLOSE c; DEALLOCATE c;
SELECT TID, amt, RunningTotal = rt
FROM #x
ORDER BY TID;
DROP TABLE #x;
SQL Server 2012 ou superior
Novas funções de janela introduzidas no SQL Server 2012 tornam essa tarefa muito mais fácil (e também tem um desempenho melhor do que todos os métodos acima):
SELECT TID, amt,
RunningTotal = SUM(amt) OVER (ORDER BY TID ROWS UNBOUNDED PRECEDING)
FROM dbo.Transactions
ORDER BY TID;
Observe que em conjuntos de dados maiores, você descobrirá que o desempenho acima é muito melhor do que qualquer uma das duas opções a seguir, pois RANGE usa um spool em disco (e o padrão usa RANGE). No entanto, também é importante observar que o comportamento e os resultados podem diferir, portanto, certifique-se de que ambos retornem resultados corretos antes de decidir entre eles com base nessa diferença.
SELECT TID, amt,
RunningTotal = SUM(amt) OVER (ORDER BY TID)
FROM dbo.Transactions
ORDER BY TID;
SELECT TID, amt,
RunningTotal = SUM(amt) OVER (ORDER BY TID RANGE UNBOUNDED PRECEDING)
FROM dbo.Transactions
ORDER BY TID;
CLR
Para completar, estou oferecendo um link para o método CLR de Pavel Pawlowski, que é de longe o método preferível em versões anteriores ao SQL Server 2012 (mas não 2000, obviamente).
http://www.pawlowski.cz/2010/09/sql-server-and-fastest-running-totals-using-clr/
Conclusão
Se você estiver no SQL Server 2012 ou superior, a escolha é óbvia - use o novo
SUM() OVER()
construção (com ROWS
vs. RANGE
). Para versões anteriores, você desejará comparar o desempenho das abordagens alternativas em seu esquema, dados e - levando em consideração fatores não relacionados ao desempenho - determinar qual abordagem é a certa para você. Pode muito bem ser a abordagem CLR. Aqui estão minhas recomendações, em ordem de preferência:SUM() OVER() ... ROWS
, se em 2012 ou superior- Método CLR, se possível
- Primeiro método CTE recursivo, se possível
- Cursor
- Os outros métodos CTE recursivos
- Atualização peculiar
- Juntar e/ou subconsulta correlacionada
Para obter mais informações com comparações de desempenho desses métodos, consulte esta pergunta em http://dba.stackexchange.com:
https://dba.stackexchange.com/questions/19507/running-total-with-count
Eu também postei mais detalhes sobre essas comparações aqui:
http://www.sqlperformance.com/2012/07/t-sql-queries/running-totals
Também para totais de execução agrupados/particionados, consulte as seguintes postagens:
http://sqlperformance.com/2014/01/t-sql-queries/grouped-running-totals
O particionamento resulta em uma consulta de totais em execução
Múltiplos Totais Executados com Agrupar Por