Calcolo della somma cumulativa in PostgreSQL


87

Voglio trovare la quantità cumulativa o corrente del campo e inserirla dallo staging alla tabella. La mia struttura di messa in scena è qualcosa del genere:

ea_month    id       amount    ea_year    circle_id
April       92570    1000      2014        1
April       92571    3000      2014        2
April       92572    2000      2014        3
March       92573    3000      2014        1
March       92574    2500      2014        2
March       92575    3750      2014        3
February    92576    2000      2014        1
February    92577    2500      2014        2
February    92578    1450      2014        3          

Voglio che la mia tabella di destinazione abbia un aspetto simile a questo:

ea_month    id       amount    ea_year    circle_id    cum_amt
February    92576    1000      2014        1           1000 
March       92573    3000      2014        1           4000
April       92570    2000      2014        1           6000
February    92577    3000      2014        2           3000
March       92574    2500      2014        2           5500
April       92571    3750      2014        2           9250
February    92578    2000      2014        3           2000
March       92575    2500      2014        3           4500
April       92572    1450      2014        3           5950

Sono davvero molto confuso su come ottenere questo risultato. Voglio ottenere questo risultato usando PostgreSQL.

Qualcuno può suggerire come fare per raggiungere questo set di risultati?


1
Come si ottiene il cum_amount di 1000 nella tabella di destinazione? Per circle_id, l'importo sembra essere 2000.

Risposte:


132

Fondamentalmente, hai bisogno di una funzione finestra . Questa è una caratteristica standard al giorno d'oggi. Oltre alle vere funzioni finestra, puoi utilizzare qualsiasi funzione aggregata come funzione finestra in Postgres aggiungendo una OVERclausola.

La difficoltà speciale qui è ottenere le partizioni e l'ordinamento corretto:

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 essere precisi:

L'opzione di inquadratura predefinita è RANGE UNBOUNDED PRECEDING, che è la stessa di RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. Con ORDER BY, questo imposta il frame in modo che tutte le righe dalla partizione si avviino attraverso l'ultimo ORDER BYpeer della riga corrente .

... che è la somma cumulativa o parziale che stai cercando. Grassetto mio.

Le righe con lo stesso valore (circle_id, ea_year, ea_month)sono "peer" in questa query. Tutti mostrano la stessa somma parziale con tutti i peer aggiunti alla somma. Ma presumo che la tua tabella sia UNIQUEattiva (circle_id, ea_year, ea_month), quindi l'ordinamento è deterministico e nessuna riga ha peer.

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

Se hai datevalori effettivi memorizzati nella tua tabella, puoi ordinarli correttamente. In caso contrario, suggerisco di sostituire ea_yeare ea_monthcon una sola colonna mondi tipo datenella 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;

Grazie per la soluzione .. Puoi aiutarmi con un'altra cosa. Voglio implementare la stessa cosa usando un cursore con la logica che ogni cerchio avrà un solo record per un mese di un anno. E la funzione dovrebbe essere eseguita una volta al mese. Come posso raggiungere questo obiettivo?
Yousuf Sultan

4
@YousufSultan: La maggior parte delle volte c'è una soluzione migliore di un cursore. È decisamente roba per una nuova domanda. Inizia una nuova domanda.
Erwin Brandstetter

Trovo questa risposta incompleta senza almeno una nota che c'è "inquadratura" in corso qui che è predefinita range unbounded preceding, che è la stessa di range between unbounded preceding and current row. Questo è il motivo sum()per cui quando viene utilizzata come funzione finestra produce un totale parziale, mentre altre funzioni finestra non hanno questo frame predefinito.
Colin 't Hart

1
@ Colin'tHart: ne ho aggiunti altri sopra per chiarire.
Erwin Brandstetter

Ecco un collegamento a una domanda simile con una query più semplice ( PARTITIONnon è sempre necessario per creare un totale parziale
Jason Axelson
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.