Como obter soma acumulada

186
declare  @t table
    (
        id int,
        SomeNumt int
    )

insert into @t
select 1,10
union
select 2,12
union
select 3,3
union
select 4,15
union
select 5,23


select * from @t

a seleção acima retorna o seguinte.

id  SomeNumt
1   10
2   12
3   3
4   15
5   23

Como obtenho o seguinte:

id  srome   CumSrome
1   10  10
2   12  22
3   3   25
4   15  40
5   23  63
ps.
fonte
5
Não é difícil obter totais em execução no T-SQL, existem muitas respostas corretas, a maioria delas bastante fáceis. O que não é fácil (ou mesmo possível no momento) é escrever uma consulta verdadeira no T-SQL para executar totais eficientes. Eles são todos O (n ^ 2), embora possam ser facilmente O (n), exceto que o T-SQL não otimiza para este caso. Você pode obter O (n) usando cursores e / ou loops While, mas depois usa cursores. ( Blech! )
RBarryYoung

Respostas:

226
select t1.id, t1.SomeNumt, SUM(t2.SomeNumt) as sum
from @t t1
inner join @t t2 on t1.id >= t2.id
group by t1.id, t1.SomeNumt
order by t1.id

Exemplo de SQL Fiddle

Resultado

| ID | SOMENUMT | SUM |
-----------------------
|  1 |       10 |  10 |
|  2 |       12 |  22 |
|  3 |        3 |  25 |
|  4 |       15 |  40 |
|  5 |       23 |  63 |

Edit: esta é uma solução generalizada que funcionará na maioria das plataformas db. Quando houver uma solução melhor disponível para sua plataforma específica (por exemplo, da Gareth), use-a!

RedFilter
fonte
12
@Franklin Somente rentável para pequenas mesas. O custo cresce proporcionalmente ao quadrado do número de linhas. O SQL Server 2012 permite que isso seja feito com muito mais eficiência.
Martin Smith
3
FWIW, tive meus dedos estalados ao fazer isso por um DBA. Eu acho que o motivo é que fica muito caro, muito rápido. Dito isto, esta é uma grande pergunta da entrevista, como analistas maioria dos dados / cientistas devem ter tido para resolver este problema de uma vez ou duas vezes :)
BenDundee
@BenDundee concordou - eu costumo fornecer soluções SQL generalizadas que funcionarão na maioria das plataformas db. Como sempre, quando houver uma abordagem melhor disponível, por exemplo, gareths, use-a!
RedFilter 29/09/15
199

A versão mais recente do SQL Server (2012) permite o seguinte.

SELECT 
    RowID, 
    Col1,
    SUM(Col1) OVER(ORDER BY RowId ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Col2
FROM tablehh
ORDER BY RowId

ou

SELECT 
    GroupID, 
    RowID, 
    Col1,
    SUM(Col1) OVER(PARTITION BY GroupID ORDER BY RowId ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Col2
FROM tablehh
ORDER BY RowId

Isso é ainda mais rápido. A versão particionada é concluída em 34 segundos em mais de 5 milhões de linhas para mim.

Agradecemos a Peso, que comentou sobre o segmento da Equipe SQL mencionado em outra resposta.

Gareth Adamson
fonte
22
Por uma questão de brevidade, você pode usar em ROWS UNBOUNDED PRECEDINGvez de ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW.
Dan
1
Nota: Se a coluna que você deseja somar cumulativamente já é uma soma ou contagem, é possível agrupar a coisa toda como uma consulta interna ou, na verdade, é possível SUM(COUNT(*)) OVER (ORDER BY RowId ROWS UNBOUNDED PRECEDING) AS CumulativeSum. Não ficou imediatamente óbvio para mim se ele iria trabalhar, mas ele fez :-)
Simon_Weaver
Disponível no PostgreSQL a partir da
versão
27

Para o SQL Server 2012 em diante, pode ser fácil:

SELECT id, SomeNumt, sum(SomeNumt) OVER (ORDER BY id) as CumSrome FROM @t

porque a ORDER BYcláusula para, SUMpor padrão, significa o RANGE UNBOUNDED PRECEDING AND CURRENT ROWquadro da janela ("Comentários gerais" em https://msdn.microsoft.com/en-us/library/ms189461.aspx )

Andrew Karakotov
fonte
13

Uma versão CTE, apenas por diversão:

;
WITH  abcd
        AS ( SELECT id
                   ,SomeNumt
                   ,SomeNumt AS MySum
             FROM   @t
             WHERE  id = 1
             UNION ALL
             SELECT t.id
                   ,t.SomeNumt
                   ,t.SomeNumt + a.MySum AS MySum
             FROM   @t AS t
                    JOIN abcd AS a ON a.id = t.id - 1
           )
  SELECT  *  FROM    abcd
OPTION  ( MAXRECURSION 1000 ) -- limit recursion here, or 0 for no limit.

Devoluções:

id          SomeNumt    MySum
----------- ----------- -----------
1           10          10
2           12          22
3           3           25
4           15          40
5           23          63
Damir Sudarevic
fonte
13

Vamos primeiro criar uma tabela com dados fictícios ->

Create Table CUMULATIVESUM (id tinyint , SomeValue tinyint)

**Now let put some data in the table**

Insert Into CUMULATIVESUM

Select 1, 10 union 
Select 2, 2  union
Select 3, 6  union
Select 4, 10 

aqui estou entrando na mesma tabela (SELF Joining)

Select c1.ID, c1.SomeValue, c2.SomeValue
From CumulativeSum c1,  CumulativeSum c2
Where c1.id >= c2.ID
Order By c1.id Asc

RESULTADO:

ID  SomeValue   SomeValue
1   10          10
2   2           10
2   2            2
3   6           10
3   6            2
3   6            6
4   10          10
4   10           2
4   10           6
4   10          10

aqui vamos nós agora somar o Somevalue de T2 e obteremos o ans

Select c1.ID, c1.SomeValue, Sum(c2.SomeValue) CumulativeSumValue
From CumulativeSum c1,  CumulativeSum c2
Where c1.id >= c2.ID
Group By c1.ID, c1.SomeValue
Order By c1.id Asc

PARA SQL SERVER 2012 e superior (desempenho muito melhor)

Select c1.ID, c1.SomeValue, 
SUM (SomeValue) OVER (ORDER BY c1.ID )
From CumulativeSum c1
Order By c1.id Asc

Resultado desejado

ID  SomeValue   CumlativeSumValue
1   10          10
2   2           12
3   6           18
4   10          28

Drop Table CumulativeSum

Limpar a tabela dummy

Neeraj Prasad Sharma
fonte
por favor edite sua resposta e formatar o código para torná-lo legível
kleopatra
E se os valores de mi "ID" forem repetidos? (eles não são chave primária na minha tabela). Não foi possível adaptar essa consulta a esse caso?
pablete
AFAIK, você precisa de um ID exclusivo para a soma acumulada e pode obtê-lo usando o número da linha. verificar que o código abaixo:; com NewTBLWITHUNiqueID como (selecione row_number () over (fim por id, somevalue) UniqueID, * De CUMULATIVESUMwithoutPK)
Neeraj Prasad Sharma
Obrigado @NeerajPrasadSharma, na verdade eu usei rank()e outra ordem por cláusula para resolvê-lo.
Pablete
5

Resposta tardia, mas mostrando mais uma possibilidade ...

A geração de soma acumulada pode ser mais otimizada com o CROSS APPLY lógica.

Funciona melhor que o INNER JOIN& OVER Clausequando analisado o plano de consulta real ...

/* Create table & populate data */
IF OBJECT_ID('tempdb..#TMP') IS NOT NULL
DROP TABLE #TMP 

SELECT * INTO #TMP 
FROM (
SELECT 1 AS id
UNION 
SELECT 2 AS id
UNION 
SELECT 3 AS id
UNION 
SELECT 4 AS id
UNION 
SELECT 5 AS id
) Tab


/* Using CROSS APPLY 
Query cost relative to the batch 17%
*/    
SELECT   T1.id, 
         T2.CumSum 
FROM     #TMP T1 
         CROSS APPLY ( 
         SELECT   SUM(T2.id) AS CumSum 
         FROM     #TMP T2 
         WHERE    T1.id >= T2.id
         ) T2

/* Using INNER JOIN 
Query cost relative to the batch 46%
*/
SELECT   T1.id, 
         SUM(T2.id) CumSum
FROM     #TMP T1
         INNER JOIN #TMP T2
                 ON T1.id > = T2.id
GROUP BY T1.id

/* Using OVER clause
Query cost relative to the batch 37%
*/
SELECT   T1.id, 
         SUM(T1.id) OVER( PARTITION BY id)
FROM     #TMP T1

Output:-
  id       CumSum
-------   ------- 
   1         1
   2         3
   3         6
   4         10
   5         15
Aditya
fonte
1
Eu não estou convencido. "Custo de consulta relativo ao lote" é algo sem sentido para comparar o desempenho das consultas. Os custos de consulta são estimativas usadas pelo planejador de consultas para avaliar rapidamente diferentes planos e escolher os menos dispendiosos, mas esses custos são para comparar planos para a mesma consulta e não são relevantes nem comparáveis entre as consultas . Esse conjunto de dados de amostra também é muito pequeno para ver qualquer diferença significativa entre os três métodos. Tente novamente com 1m de linhas, veja os planos de execução reais, tente com set io statistics one compare o tempo da CPU e o tempo real.
Davos
4

Select *, (Select SUM(SOMENUMT) From @t S Where S.id <= M.id) From @t M

Ritesh Khatri
fonte
É uma maneira muito inteligente de obter o resultado e você pode adicionar várias condições à soma.
RaRdEvA
@RaRdEvA Porém, não é ótimo para desempenho, ele é executado correlated subqueryem todas as linhas do conjunto de resultados, varrendo cada vez mais linhas. Ele não mantém um total em execução e verifica os dados uma vez, como as funções da janela.
Davos
1
@ David você está certo, se você usá-lo fica muito lento mais de 100.000 registros.
RaRdEvA 28/01/19
2

Você pode usar esta consulta simples para cálculo progressivo:

select 
   id
  ,SomeNumt
  ,sum(SomeNumt) over(order by id ROWS between UNBOUNDED PRECEDING and CURRENT ROW) as CumSrome
from @t
Afif Pratama
fonte
1

Depois que a tabela é criada -

select 
    A.id, A.SomeNumt, SUM(B.SomeNumt) as sum
    from @t A, @t B where A.id >= B.id
    group by A.id, A.SomeNumt

order by A.id
Porsh
fonte
1

Acima (Pré-SQL12), vemos exemplos como este: -

SELECT
    T1.id, SUM(T2.id) AS CumSum
FROM 
    #TMP T1
    JOIN #TMP T2 ON T2.id < = T1.id
GROUP BY
    T1.id

Mais eficiente...

SELECT
    T1.id, SUM(T2.id) + T1.id AS CumSum
FROM 
    #TMP T1
    JOIN #TMP T2 ON T2.id < T1.id
GROUP BY
    T1.id
Julian
fonte
0

Tente isto

select 
    t.id,
    t.SomeNumt, 
    sum(t.SomeNumt) Over (Order by t.id asc Rows Between Unbounded Preceding and Current Row) as cum
from 
    @t t 
group by
    t.id,
    t.SomeNumt
order by
    t.id asc;
bellonid
fonte
Isso funciona com o SQL Server 2012 e posteriores, 2008 possui suporte limitado para funções da janela.
22616 Peter Smit
0

Tente o seguinte:

CREATE TABLE #t(
 [name] varchar NULL,
 [val] [int] NULL,
 [ID] [int] NULL
) ON [PRIMARY]

insert into #t (id,name,val) values
 (1,'A',10), (2,'B',20), (3,'C',30)

select t1.id, t1.val, SUM(t2.val) as cumSum
 from #t t1 inner join #t t2 on t1.id >= t2.id
 group by t1.id, t1.val order by t1.id
Sachin
fonte
0

A solução SQL que combina "LINHAS ENTRE PRECEDENTES NÃO LIMITADOS E LINHA ATUAL" e "SUM" fez exatamente o que eu queria alcançar. Muito obrigado!

Se pode ajudar alguém, aqui estava o meu caso. Eu queria acumular +1 em uma coluna sempre que um criador fosse encontrado como "Algum Criador" (exemplo). Caso contrário, não há incremento, mas mostra o resultado do incremento anterior.

Então, este pedaço de SQL:

SUM( CASE [rmaker] WHEN 'Some Maker' THEN  1 ELSE 0 END) 
OVER 
(PARTITION BY UserID ORDER BY UserID,[rrank] ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Cumul_CNT

Permitiu-me obter algo parecido com isto:

User 1  Rank1   MakerA      0  
User 1  Rank2   MakerB      0  
User 1  Rank3   Some Maker  1  
User 1  Rank4   Some Maker  2  
User 1  Rank5   MakerC      2
User 1  Rank6   Some Maker  3  
User 2  Rank1   MakerA      0  
User 2  Rank2   SomeMaker   1  

Explicação acima: Inicia a contagem de "some maker" com 0, Some Maker é encontrado e fazemos +1. Para o usuário 1, MakerC é encontrado, portanto, não fazemos +1, mas a contagem vertical de Some Maker fica presa a 2 até a próxima linha. O particionamento é por usuário, portanto, quando mudamos de usuário, a contagem acumulada volta a zero.

Estou no trabalho, não quero mérito nesta resposta, apenas agradeça e mostre meu exemplo caso alguém esteja na mesma situação. Eu estava tentando combinar SUM e PARTITION, mas a incrível sintaxe "LINHAS ENTRE PRECEDENTES NÃO LIMITADOS E LINHAS ATUAIS" concluiu a tarefa.

Obrigado! Groaker

Groaker
fonte
0

Sem usar qualquer tipo de salário acumulado JOIN para uma pessoa buscar usando a consulta a seguir:

SELECT * , (
  SELECT SUM( salary ) 
  FROM  `abc` AS table1
  WHERE table1.ID <=  `abc`.ID
    AND table1.name =  `abc`.Name
) AS cum
FROM  `abc` 
ORDER BY Name
Rudar Daman Singla
fonte
0

Para Ex: se você tiver uma tabela com duas colunas, uma é ID e a segunda é número e deseja descobrir a soma acumulada.

SELECT ID,Number,SUM(Number)OVER(ORDER BY ID) FROM T
JP
fonte