Comment obtenir la somme cumulée


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 sélection ci-dessus me renvoie ce qui suit.

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

Comment puis-je obtenir les éléments suivants:

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

5
Obtenir les totaux en cours d'exécution dans T-SQL n'est pas difficile, il existe de nombreuses réponses correctes, la plupart d'entre elles assez faciles. Ce qui n'est pas facile (ou même possible pour le moment) est d'écrire une vraie requête en T-SQL pour exécuter des totaux qui soit efficace. Ils sont tous O (n ^ 2), bien qu'ils puissent facilement être O (n), sauf que T-SQL n'optimise pas dans ce cas. Vous pouvez obtenir O (n) en utilisant des curseurs et / ou des boucles While, mais vous utilisez des curseurs. ( blech! )
RBarryYoung

Réponses:


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

Exemple de SQL Fiddle

Production

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

Edit: c'est une solution généralisée qui fonctionnera sur la plupart des plates-formes de base de données. Lorsqu'une meilleure solution est disponible pour votre plate-forme spécifique (par exemple, gareth), utilisez-la!


12
@Franklin Uniquement rentable pour les petites tables. Le coût augmente proportionnellement au carré du nombre de lignes. SQL Server 2012 permet de faire cela beaucoup plus efficacement.
Martin Smith

3
FWIW, j'ai eu mes doigts claqués en faisant cela par un DBA. Je pense que la raison en est que cela devient vraiment cher, très rapide. Cela étant dit, c'est une excellente question d'entretien, car la plupart des analystes de données / scientifiques auraient dû résoudre ce problème une ou deux fois :)
BenDundee

@BenDundee D'accord - J'ai tendance à fournir des solutions SQL généralisées qui fonctionneront sur la plupart des plates-formes de base de données. Comme toujours, quand il y a une meilleure approche disponible, par exemple, gareths, utilisez-la!
RedFilter le

199

La dernière version de SQL Server (2012) permet ce qui suit.

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

C'est encore plus rapide. La version partitionnée se termine en 34 secondes sur 5 millions de lignes pour moi.

Merci à Peso, qui a commenté le fil de discussion SQL Team mentionné dans une autre réponse.


22
Par souci de concision, vous pouvez utiliser à la ROWS UNBOUNDED PRECEDINGplace de ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW.
Dan

1
Remarque: Si la colonne que vous souhaitez additionner de manière cumulative est elle-même déjà une somme ou un nombre, vous pouvez soit envelopper le tout en tant que requête interne, soit le faire SUM(COUNT(*)) OVER (ORDER BY RowId ROWS UNBOUNDED PRECEDING) AS CumulativeSum. Ce n'était pas immédiatement évident pour moi si cela fonctionnerait, mais cela a fonctionné :-)
Simon_Weaver

Disponible dans PostgreSQL à partir de la version 8.4: postgresql.org/docs/8.4/sql-select.html
ADJenks


13

Une version CTE, juste pour le plaisir:

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

Retour:

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

13

Commençons par créer une table avec des données factices ->

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 

ici je rejoins la même table (SELF Joining)

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

RÉSULTAT :

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

nous allons maintenant additionner simplement la valeur de t2 et nous obtiendrons les 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

POUR SQL SERVER 2012 et supérieur (bien plus performant)

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

Résultat désiré

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

Drop Table CumulativeSum

Effacer le mannequin


veuillez modifier votre réponse et formater le code pour le rendre lisible
kleopatra

Que faire si les valeurs mi "ID" sont répétées? (ce ne sont évidemment pas des clés primaires dans ma table) Je n'ai pas pu adapter cette requête à ce cas?
pablete

AFAIK vous avez besoin d'un identifiant unique pour la somme cumulée, et vous pouvez l'obtenir en utilisant row_number. vérifiez ce code ci-dessous:; avec NewTBLWITHUNiqueID as (sélectionnez row_number () over (order by id, somevalue) UniqueID, * From CUMULATIVESUMwithoutPK)
Neeraj Prasad Sharma

Merci @NeerajPrasadSharma, j'ai effectivement utilisé rank()et un autre ordre par clause pour le résoudre.
pablete

5

Réponse tardive mais montrant une autre possibilité ...

La génération de somme cumulative peut être plus optimisée avec la CROSS APPLYlogique.

Fonctionne mieux que le INNER JOIN& une OVER Clausefois analysé le plan de requête réel ...

/* 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
Je ne suis pas convaincu. Le "coût des requêtes par rapport au lot" n'a aucun sens pour comparer les performances des requêtes. Les coûts de requête sont des estimations utilisées par le planificateur de requêtes pour évaluer rapidement différents plans et choisir le moins coûteux, mais ces coûts servent à comparer les plans pour la même requête et ne sont pas pertinents ou comparables entre les requêtes , pas du tout. Cet exemple de jeu de données est également trop petit pour voir une différence significative entre les trois méthodes. Essayez à nouveau avec 1m de lignes, regardez les plans d'exécution réels, essayez-le avec set io statistics onet comparez le processeur et les temps réels.
Davos

4

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


C'est un moyen très intelligent d'obtenir le résultat, et vous pouvez ajouter plusieurs conditions à la somme.
RaRdEvA

@RaRdEvA Ce n'est pas génial pour les performances cependant, il exécute cela correlated subquerypour chaque ligne du jeu de résultats, analysant de plus en plus de lignes au fur et à mesure. Il ne garde pas un total courant et scanne les données une fois comme le peuvent les fonctions de fenêtre.
Davos

1
@Davos vous avez raison, si vous l'utilisez, cela devient très lent sur 100 000 enregistrements.
RaRdEvA


2

Vous pouvez utiliser cette requête simple pour le calcul progressif:

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

1

Une fois la table créée -

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

Ci-dessus (Pre-SQL12), nous voyons des exemples comme celui-ci: -

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

Plus efficace...

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

Essaye ça

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;

Cela fonctionne avec SQL Server 2012 et plus, 2008 a une prise en charge limitée des fonctions de fenêtre.
Peter Smit

0

Essaye ça:

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 solution SQL qui combine «RANGS ENTRE RANGÉE PRÉCÉDENTE ET ACTUELLE NON LIMITÉE» et «SOMME» a fait exactement ce que je voulais réaliser. Merci beaucoup!

Si cela peut aider quelqu'un, voici mon cas. Je voulais cumuler +1 dans une colonne chaque fois qu'un créateur est trouvé comme "Some Maker" (exemple). Sinon, pas d'incrément mais afficher le résultat de l'incrément précédent.

Donc ce morceau 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

M'a permis d'obtenir quelque chose comme ça:

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  

Explication de ci-dessus: Cela commence le décompte de "certains fabricants" avec 0, Some Maker est trouvé et nous faisons +1. Pour l'utilisateur 1, MakerC est trouvé, nous ne faisons donc pas +1 mais le décompte vertical de Some Maker est bloqué à 2 jusqu'à la ligne suivante. Le partitionnement est par l'utilisateur, donc lorsque nous changeons d'utilisateur, le nombre cumulé est de retour à zéro.

Je suis au travail, je ne veux aucun mérite sur cette réponse, dis simplement merci et montre mon exemple au cas où quelqu'un serait dans la même situation. J'essayais de combiner SOMME et PARTITION mais la syntaxe étonnante «RANGÉES ENTRE LA RANGÉE PRÉCÉDENTE ET ACTUELLE NON LIMITÉE» a complété la tâche.

Merci! Groaker


0

Sans utiliser aucun type de salaire cumulatif JOIN pour une personne, récupérez à l'aide de la requête suivante:

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

Pour Ex: SI vous avez une table avec deux colonnes, une est ID et la seconde est un nombre et souhaite connaître la somme cumulée.

SELECT ID,Number,SUM(Number)OVER(ORDER BY ID) FROM T
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.