sql >> Base de Datos >  >> RDS >> PostgreSQL

Cálculo de la suma acumulada en PostgreSQL

Básicamente, necesitas una función de ventana. Esa es una característica estándar hoy en día. Además de las funciones de ventana genuinas, puede usar cualquier función agregada como función de ventana en Postgres agregando un OVER cláusula.

La dificultad especial aquí es obtener las particiones y el orden correcto:

SELECT ea_month, id, amount, ea_year, circle_id
     , sum(amount) OVER (PARTITION BY circle_id
                         ORDER BY ea_year, ea_month) AS cum_amt
FROM   tbl
ORDER  BY circle_id, month;

Y no GROUP BY .

La suma de cada fila se calcula desde la primera fila de la partición hasta la fila actual, o citando el manual para ser precisos:

La opción de encuadre predeterminada es RANGE UNBOUNDED PRECEDING , que es lo mismo que RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW . Con ORDER BY , esto establece que el marco sea todas las filas desde el inicio de la partición hasta el último ORDER BY de la fila actual compañero .

... que es la suma acumulada o acumulada que busca. Énfasis en negrita mío.

Filas con el mismo (circle_id, ea_year, ea_month) son "compañeros" en esta consulta. Todos ellos muestran la misma suma acumulada con todos los pares agregados a la suma. Pero supongo que su tabla es UNIQUE en (circle_id, ea_year, ea_month) , entonces el orden de clasificación es determinista y ninguna fila tiene pares.

Postgres 11 agregó herramientas para incluir/excluir pares con el nuevo frame_exclusion opciones Ver:

  • Agregar todos los valores que no están en el mismo grupo

Ahora, ORDER BY ... ea_month no funcionará con cadenas para nombres de meses . Postgres ordenaría alfabéticamente según la configuración regional.

Si tiene una date real valores almacenados en su tabla que puede ordenar correctamente. Si no, sugiero reemplazar ea_year y ea_month con una sola columna mon de tipo date en tu mesa.

  • Transforma lo que tienes con to_date() :

      to_date(ea_year || ea_month , 'YYYYMonth') AS mon
    
  • Para mostrar, puede obtener cadenas originales con to_char() :

      to_char(mon, 'Month') AS ea_month
      to_char(mon, 'YYYY') AS ea_year
    

Mientras esté atascado con el desafortunado diseño, esto funcionará:

SELECT ea_month, id, amount, ea_year, circle_id
     , sum(amount) OVER (PARTITION BY circle_id ORDER BY mon) AS cum_amt
FROM   (SELECT *, to_date(ea_year || ea_month, 'YYYYMonth') AS mon FROM tbl)
ORDER  BY circle_id, mon;