Come ottenere la somma cumulativa


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

la selezione precedente mi restituisce quanto segue.

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

Come posso ottenere quanto segue:

id  srome   CumSrome
1   10  10
2   12  22
3   3   25
4   15  40
5   23  63

5
Ottenere i totali in esecuzione in T-SQL non è difficile, ci sono molte risposte corrette, molte delle quali abbastanza facili. Ciò che non è facile (o addirittura possibile in questo momento) è scrivere una vera query in T-SQL per eseguire totali efficienti. Sono tutti O (n ^ 2), sebbene possano essere facilmente O (n), tranne per il fatto che T-SQL non ottimizza in questo caso. Puoi ottenere O (n) usando i cursori e / o i cicli While, ma poi stai usando i cursori. ( blech! )
RBarryYoung

Risposte:


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

Esempio di violino SQL

Produzione

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

Modifica: questa è una soluzione generalizzata che funzionerà sulla maggior parte delle piattaforme db. Quando è disponibile una soluzione migliore per la tua piattaforma specifica (ad es. Gareth's), usala!


12
@Franklin Conveniente solo per tavolini. Il costo cresce proporzionalmente al quadrato del numero di righe. SQL Server 2012 consente di eseguire questa operazione in modo molto più efficiente.
Martin Smith,

3
FWIW, mi sono fatto schioccare le dita quando lo facevo da un DBA. Penso che la ragione sia che diventa molto costoso, molto veloce. Detto questo, questa è un'ottima domanda per l'intervista, poiché la maggior parte degli analisti / scienziati di dati avrebbero dovuto risolvere questo problema una o due volte :)
BenDundee,

@BenDundee Concordato - Tendo a fornire soluzioni SQL generalizzate che funzioneranno sulla maggior parte delle piattaforme db. Come sempre, quando è disponibile un approccio migliore, ad esempio Gareths, usalo!
RedFilter

199

L'ultima versione di SQL Server (2012) consente quanto segue.

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

o

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

Questo è ancora più veloce. La versione partizionata si completa in 34 secondi per 5 milioni di righe.

Grazie a Peso, che ha commentato il thread del team SQL indicato in un'altra risposta.


22
Per brevità, è possibile utilizzare ROWS UNBOUNDED PRECEDINGinvece di ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW.
Dan

1
Nota: se la colonna che si desidera sommare è già essa stessa una somma o un conteggio, è possibile racchiudere il tutto come una query interna oppure è possibile farlo effettivamente SUM(COUNT(*)) OVER (ORDER BY RowId ROWS UNBOUNDED PRECEDING) AS CumulativeSum. Non è stato immediatamente ovvio per me se funzionasse, ma ha funzionato :-)
Simon_Weaver

Disponibile in PostgreSQL a partire
dall'8.4


13

Una versione CTE, solo per divertimento:

;
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.

Ritorna:

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

13

Consente innanzitutto di creare una tabella con dati fittizi ->

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 

qui sto unendo la stessa tabella (SELF Joining)

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

RISULTATO:

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

qui andiamo ora sommando il valore di Som di t2 e otterremo l'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

PER SQL SERVER 2012 e versioni successive (prestazioni molto migliori)

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

Risultato desiderato

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

Drop Table CumulativeSum

Cancella il manichino


modifica la risposta e formatta il codice per renderlo leggibile
kleopatra,

Cosa succede se i valori "ID" mi vengono ripetuti? (ovviamente non sono la chiave primaria nella mia tabella) Non sono stato in grado di adattare questa query a quel caso?
pablete,

AFAIK hai bisogno di un ID univoco per la somma cumulativa e puoi ottenerlo usando row_number. controlla quel codice qui sotto:; con NewTBLWITHUNiqueID come (seleziona row_number () sopra (ordina per id, somevalue) UniqueID, * From CUMULATIVESUMwithoutPK)
Neeraj Prasad Sharma

Grazie @NeerajPrasadSharma, ho effettivamente utilizzato rank()e un altro ordine per clausola per risolverlo.
pablete,

5

Risposta in ritardo ma mostrando un'altra possibilità ...

La generazione della somma cumulativa può essere più ottimizzata con la CROSS APPLYlogica.

Funziona meglio del INNER JOIN& OVER Clausequando analizzato il piano di query effettivo ...

/* 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

1
Non sono persuaso. "Costo della query relativo al batch" è una cosa insignificante per il confronto delle prestazioni delle query. I costi delle query sono stime utilizzate dal pianificatore di query per valutare rapidamente diversi piani e scegliere quelli meno costosi, ma tali costi sono per il confronto dei piani per la stessa query e non sono pertinenti o confrontabili tra le query , per niente. Anche questo set di dati di esempio è troppo piccolo per vedere una differenza significativa tra i tre metodi. Riprovare con 1 m di righe, esaminare i piani di esecuzione effettivi, provarlo con set io statistics one confrontare la CPU e i tempi effettivi.
Davos,

4

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


È un modo molto intelligente per ottenere il risultato e puoi aggiungere più condizioni alla somma.
RaRdEvA,

@RaRdEvA Non è eccezionale per le prestazioni, ma lo esegue correlated subqueryper ogni singola riga del set di risultati, scansionando sempre più righe mentre procede. Non mantiene un totale parziale e scansiona i dati una volta come possono fare le funzioni della finestra.
Davos,

1
@Davos hai ragione, se lo usi diventa molto più lento di oltre 100.000 record.
RaRdEvA,


2

È possibile utilizzare questa semplice query per il calcolo progressivo:

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

1

Una volta creata la tabella -

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

1

Sopra (Pre-SQL12) vediamo esempi come questo: -

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

Più efficiente...

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

0

Prova questo

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;

Funziona con SQL Server 2012 e versioni successive, il 2008 ha un supporto limitato per le funzioni della finestra.
Peter Smit,

0

Prova questo:

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

0

La soluzione SQL che combina "ROWS TRA PRECEDING UNBOUNDED E ROW CORRENTE" e "SUM" ha fatto esattamente quello che volevo ottenere. Grazie mille!

Se può aiutare qualcuno, ecco il mio caso. Volevo accumulare +1 in una colonna ogni volta che un creatore viene trovato come "Some Maker" (esempio). In caso contrario, nessun incremento ma mostra il risultato dell'incremento precedente.

Quindi questo pezzo di 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

Mi ha permesso di ottenere qualcosa del genere:

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  

Spiegazione di cui sopra: inizia il conteggio di "some maker" con 0, Some Maker viene trovato e facciamo +1. Per l'utente 1, MakerC viene trovato, quindi non facciamo +1 ma invece il conteggio verticale di Some Maker è bloccato su 2 fino alla riga successiva. Il partizionamento è per utente, quindi quando cambiamo utente, il conteggio cumulativo torna a zero.

Sono al lavoro, non voglio alcun merito su questa risposta, basta dire grazie e mostrare il mio esempio nel caso in cui qualcuno si trovi nella stessa situazione. Stavo cercando di combinare SUM e PARTITION, ma l'incredibile sintassi "ROWS TRA PRECEDING UNBOUNDED AND ROW ROW" ha completato il compito.

Grazie! Groaker


0

Senza utilizzare alcun tipo di JOIN stipendio cumulativo per una persona prendere utilizzando la seguente query:

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

0

Ad esempio: SE hai una tabella con due colonne una è ID e la seconda è numero e vuoi scoprire la somma cumulativa.

SELECT ID,Number,SUM(Number)OVER(ORDER BY ID) FROM T
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.