PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Calcolo della somma cumulativa in PostgreSQL

Fondamentalmente, hai bisogno di una funzione finestra. Questa è una caratteristica standard al giorno d'oggi. Oltre alle funzioni della finestra originali, puoi utilizzare qualsiasi funzione di aggregazione come funzione finestra in Postgres aggiungendo un OVER clausola.

La difficoltà speciale qui è ottenere partizioni e ordinare correttamente:

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;

E no GROUP BY .

La somma per ogni riga viene calcolata dalla prima riga nella partizione alla riga corrente, o citando il manuale per la precisione:

L'opzione di inquadratura predefinita è RANGE UNBOUNDED PRECEDING , che è lo stesso di RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW . Con ORDER BY , questo imposta il frame in modo che sia tutte le righe dalla partizione dall'inizio fino all'ultimo ORDER BY della riga corrente pari .

... che è la somma cumulativa o parziale che stai cercando. Enfasi in grassetto la mia.

Righe con lo stesso (circle_id, ea_year, ea_month) sono "coetanei" in questa domanda. Tutti quelli mostrano la stessa somma parziale con tutti i peer aggiunti alla somma. Ma presumo che la tua tabella sia UNIQUE su (circle_id, ea_year, ea_month) , quindi l'ordinamento è deterministico e nessuna riga ha peer.

Postgres 11 ha aggiunto strumenti per includere/escludere peer con il nuovo frame_exclusion opzioni. Vedi:

  • Aggregazione di tutti i valori non nello stesso gruppo

Ora, ORDER BY ... ea_month non funzionerà con le stringhe per i nomi dei mesi . Postgres ordina in ordine alfabetico in base alle impostazioni locali.

Se hai una date effettiva i valori memorizzati nella tabella possono essere ordinati correttamente. In caso contrario, suggerisco di sostituire ea_year e ea_month con una singola colonna mon di tipo date nella tua tabella.

  • Trasforma ciò che hai con to_date() :

      to_date(ea_year || ea_month , 'YYYYMonth') AS mon
    
  • Per la visualizzazione, puoi ottenere stringhe originali con to_char() :

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

Anche se bloccato con lo sfortunato design, funzionerà:

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;