Cómo obtener una suma acumulativa


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 selección anterior me devuelve lo siguiente.

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

¿Cómo obtengo lo siguiente?

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

55
Obtener totales de ejecución en T-SQL no es difícil, hay muchas respuestas correctas, la mayoría de ellas bastante fáciles. Lo que no es fácil (o incluso posible en este momento) es escribir una consulta verdadera en T-SQL para ejecutar totales que sea eficiente. Todos son O (n ^ 2), aunque fácilmente podrían ser O (n), excepto que T-SQL no se optimiza para este caso. Puede obtener O (n) usando los cursores y / o los bucles While, pero luego está usando los cursores. ( ¡blech! )
RBarryYoung

Respuestas:


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

Ejemplo de SQL Fiddle

Salida

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

Editar: esta es una solución generalizada que funcionará en la mayoría de las plataformas db. Cuando haya una mejor solución disponible para su plataforma específica (por ejemplo, la de Gareth), ¡úsela!


12
@Franklin Solo rentable para mesas pequeñas. El costo crece proporcionalmente al cuadrado del número de filas. SQL Server 2012 permite que esto se haga de manera mucho más eficiente.
Martin Smith

3
FWIW, un DBA me golpeó los nudillos al hacer esto. Creo que la razón es que se vuelve muy cara, muy rápida. Dicho esto, esta es una gran pregunta de entrevista, ya que la mayoría de los analistas de datos / científicos deberían haber tenido que resolver este problema una o dos veces :)
BenDundee

@BenDundee estuvo de acuerdo: tiendo a proporcionar soluciones SQL generalizadas que funcionarán en la mayoría de las plataformas db. Como siempre, cuando hay un mejor enfoque disponible, por ejemplo, gareths, ¡úsalo!
RedFilter

199

La última versión de SQL Server (2012) permite lo siguiente.

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

Esto es aún más rápido. La versión particionada se completa en 34 segundos en más de 5 millones de filas para mí.

Gracias a Peso, quien comentó sobre el hilo del Equipo SQL mencionado en otra respuesta.


22
Por brevedad, puede usar en ROWS UNBOUNDED PRECEDINGlugar de ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW.
Dan

1
Nota: Si la columna que desea sumar acumulativamente ya es una suma o un recuento, puede ajustar todo como una consulta interna o puede hacerlo SUM(COUNT(*)) OVER (ORDER BY RowId ROWS UNBOUNDED PRECEDING) AS CumulativeSum. No era obvio para mí si funcionaría, pero funcionó :-)
Simon_Weaver

Disponible en PostgreSQL a partir de 8.4: postgresql.org/docs/8.4/sql-select.html
ADJenks


13

Una versión CTE, solo por diversión:

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

Devoluciones:

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

13

Primero creemos una tabla con datos ficticios ->

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 

aquí me estoy uniendo a la misma mesa (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

aquí vamos ahora solo sumamos el Somevalue de t2 y obtendremos el 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 y superior (mucho mejor rendimiento)

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

Resultado deseado

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

Drop Table CumulativeSum

Despeja la mesa ficticia


edite su respuesta y formatee el código para que sea legible
kleopatra

¿Qué pasa si se repiten los valores de mi "ID"? (obviamente no son la clave principal en mi tabla) ¿No he podido adaptar esta consulta a ese caso?
Pablete

AFAIK necesita una identificación única para la suma acumulativa, y puede obtenerla usando row_number. verifique ese código a continuación:; con NewTBLWITHUNiqueID como (seleccione row_number () over (ordenar por id, somevalue) UniqueID, * From CUMULATIVESUMwithoutPK)
Neeraj Prasad Sharma

Gracias @NeerajPrasadSharma, realmente utilicé rank()y otra orden por cláusula para resolverlo.
Pablete

5

Respuesta tardía pero mostrando una posibilidad más ...

La generación de suma acumulativa puede optimizarse más con la CROSS APPLYlógica.

Funciona mejor que INNER JOIN& OVER Clausecuando se analiza el plan 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

1
No estoy persuadido El "costo de consulta relativo al lote" no tiene sentido para comparar el rendimiento de las consultas. Los costos de consulta son estimaciones utilizadas por el planificador de consultas para sopesar rápidamente diferentes planes y elegir el menos costoso, pero esos costos son para comparar planes para la misma consulta , y no son relevantes o comparables entre consultas , en absoluto. Este conjunto de datos de muestra también es demasiado pequeño para ver una diferencia significativa entre los tres métodos. Inténtalo de nuevo con 1 millón de filas, mira los planes de ejecución reales, pruébalo set io statistics ony compara la CPU y los tiempos reales.
Davos

4

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


Es una forma muy inteligente de lograr el resultado, y puede agregar múltiples condiciones a la suma.
RaRdEvA

@RaRdEvA Sin embargo, no es excelente para el rendimiento, lo ejecuta correlated subquerypara cada fila del conjunto de resultados, escaneando más y más filas a medida que avanza. No mantiene un total acumulado y escanea los datos una vez como pueden hacerlo las funciones de ventana.
Davos

1
@Davos tienes razón, si lo usas se vuelve muy lento con más de 100,000 registros.
RaRdEvA


2

Puede usar esta consulta simple para el cálculo progresivo:

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

1

Una vez que se crea la tabla,

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

Arriba (Pre-SQL12) vemos ejemplos 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

Más 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

0

Prueba esto

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;

Esto funciona con SQL Server 2012 y versiones posteriores, 2008 tiene soporte limitado para funciones de ventana.
Peter Smit

0

Prueba esto:

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 solución SQL que combina "FILAS ENTRE PRECEDENTES SIN BORDE Y FILA ACTUAL" y "SUMA" hizo exactamente lo que quería lograr. Muchas gracias!

Si puede ayudar a alguien, aquí estaba mi caso. Quería acumular +1 en una columna cada vez que se encuentra un creador como "Some Maker" (ejemplo). Si no, no hay incremento pero muestra el resultado del incremento anterior.

Entonces esta pieza 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

Me permitió obtener algo como esto:

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  

Explicación de lo anterior: comienza el conteo de "algún creador" con 0, se encuentra Some Maker y hacemos +1. Para el Usuario 1, se encuentra MakerC, por lo que no hacemos +1, sino que el recuento vertical de Some Maker está pegado a 2 hasta la siguiente fila. La partición es por usuario, por lo que cuando cambiamos de usuario, el recuento acumulativo vuelve a cero.

Estoy en el trabajo, no quiero ningún mérito en esta respuesta, solo agradezco y muestre mi ejemplo en caso de que alguien esté en la misma situación. Estaba tratando de combinar SUMA y PARTICIÓN, pero la increíble sintaxis "FILAS ENTRE LAS PREDAS SIN PREDEDOR Y LA FILA ACTUAL" completó la tarea.

¡Gracias! Groaker


0

Sin utilizar ningún tipo de salario acumulativo de JOIN para una persona mediante el uso de la siguiente consulta:

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

Por ejemplo: SI tiene una tabla con dos columnas, una es ID y la segunda es número y quiere saber la suma acumulativa.

SELECT ID,Number,SUM(Number)OVER(ORDER BY ID) FROM T
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.