Berechnung der kumulativen Summe in PostgreSQL

86

Ich möchte die kumulative oder laufende Feldmenge ermitteln und von der Bereitstellung in die Tabelle einfügen. Meine Inszenierungsstruktur sieht ungefähr so ​​aus:

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          

Ich möchte, dass meine Zieltabelle ungefähr so ​​aussieht:

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

Ich bin wirklich sehr verwirrt darüber, wie ich dieses Ergebnis erzielen soll. Ich möchte dieses Ergebnis mit PostgreSQL erzielen.

Kann jemand vorschlagen, wie diese Ergebnismenge erreicht werden soll?

Yousuf Sultan
quelle
1
Wie erhalten Sie den cum_amount von 1000 in Ihrer Zieltabelle? Für circle_id scheint der Betrag 2000 zu sein.

Antworten:

132

Grundsätzlich benötigen Sie eine Fensterfunktion . Das ist heutzutage eine Standardfunktion. Zusätzlich zu echten Fensterfunktionen können Sie in Postgres jede Aggregatfunktion als Fensterfunktion verwenden, indem Sie eine OVERKlausel anhängen .

Die besondere Schwierigkeit besteht darin, Partitionen und Sortierreihenfolge richtig zu machen:

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;

Und nein GROUP BY .

Die Summe für jede Zeile wird von der ersten Zeile in der Partition bis zur aktuellen Zeile berechnet - oder genauer gesagt im Handbuch :

Die Standard-Framing-Option RANGE UNBOUNDED PRECEDINGist die gleiche wie RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. Mit ORDER BYwird der Frame so festgelegt, dass alle Zeilen von der Partition bis zum letzten ORDER BYPeer der aktuellen Zeile gestartet werden .

... das ist die kumulative oder laufende Summe, nach der Sie suchen. Meine kühne Betonung.

Zeilen mit demselben (circle_id, ea_year, ea_month)sind "Peers" in dieser Abfrage. Alle zeigen die gleiche laufende Summe, wobei alle Peers zur Summe hinzugefügt werden. Aber ich nehme an, Ihre Tabelle UNIQUEauf (circle_id, ea_year, ea_month), dann ist die Sortierreihenfolge deterministisch ist und keine Zeile hat Peers.

Funktioniert jetzt ORDER BY ... ea_month nicht mit Zeichenfolgen für Monatsnamen . Postgres würde alphabetisch nach der Ländereinstellung sortieren.

Wenn Sie tatsächliche dateWerte in Ihrer Tabelle gespeichert haben , können Sie diese ordnungsgemäß sortieren. Wenn nicht, schlage ich zu ersetzen ea_yearund ea_monthmit einer einzigen Spalte monvom Typ datein der Tabelle.

  • Verwandeln Sie, was Sie haben mit to_date():

      to_date(ea_year || ea_month , 'YYYYMonth') AS mon
    
  • Für die Anzeige können Sie Originalzeichenfolgen erhalten mit to_char():

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

Während mit dem unglücklichen Design stecken, wird dies funktionieren:

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;
Erwin Brandstetter
quelle
Vielen Dank für die Lösung. Können Sie mir noch bei einer Sache helfen? Ich möchte dasselbe mit einem Cursor implementieren, wobei die Logik lautet, dass jeder Kreis nur einen Datensatz für einen Monat eines Jahres hat. Und die Funktion soll einmal im Monat ausgeführt werden. Wie kann ich das erreichen?
Yousuf Sultan
4
@YousufSultan: Meistens gibt es eine bessere Lösung als einen Cursor. Das ist definitiv Zeug für eine neue Frage. Bitte starten Sie eine neue Frage.
Erwin Brandstetter
Ich finde diese Antwort unvollständig, ohne zumindest zu bemerken, dass hier ein "Framing" stattfindet, das standardmäßig range unbounded precedingden gleichen Wert hat wie range between unbounded preceding and current row. Aus diesem Grund wird sum()bei Verwendung als Fensterfunktion eine laufende Summe erzeugt, während andere Fensterfunktionen diesen Standardrahmen nicht haben.
Colin 't Hart
1
@ Colin'tHart: Ich habe oben noch etwas hinzugefügt, um das zu verdeutlichen.
Erwin Brandstetter
Hier ist ein Link zu einer ähnlichen Frage mit einer einfacheren Abfrage (die PARTITIONnicht immer benötigt wird, um eine laufende Summe zu erstellen): stackoverflow.com/a/5700744/175830
Jason Axelson