Obliczanie sumy skumulowanej w PostgreSQL

85

Chcę znaleźć skumulowaną lub bieżącą ilość pola i wstawić ją z przemieszczania do tabeli. Moja struktura inscenizacji wygląda mniej więcej tak:

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          

Chcę, aby moja tabela docelowa wyglądała mniej więcej tak:

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

Jestem naprawdę zdezorientowany, jak osiągnąć ten wynik. Chcę osiągnąć ten wynik za pomocą PostgreSQL.

Czy ktoś może zasugerować, jak zabrać się do osiągnięcia tego zestawu wyników?

Yousuf Sultan
źródło
1
Jak uzyskać cum_amount równą 1000 w tabeli docelowej? W przypadku circle_id kwota wydaje się wynosić 2000.

Odpowiedzi:

130

Zasadniczo potrzebujesz funkcji okna . To standardowa funkcja w dzisiejszych czasach. Oprócz oryginalnych funkcji okna, możesz użyć dowolnej funkcji agregującej jako funkcji okna w Postgresie, dołączając OVERklauzulę.

Szczególna trudność polega na tym, aby uzyskać partycje i porządek sortowania:

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;

I nie GROUP BY .

Suma dla każdego wiersza jest obliczana od pierwszego wiersza w partycji do bieżącego wiersza - lub dokładniej cytując instrukcję :

Domyślną opcją kadrowania jest RANGE UNBOUNDED PRECEDINGto samo, co RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. Dzięki ORDER BYtemu ustawia ramkę na wszystkie wiersze od początku partycji do ostatniego ORDER BYpeera bieżącego wiersza .

... która jest sumą skumulowaną lub sumaryczną, o którą myślisz. Odważne podkreślenie moje.

Wiersze z samo (circle_id, ea_year, ea_month)to „rówieśnicy” w tym zapytaniu. Wszystkie z nich pokazują tę samą sumę kumulacyjną z dodanymi do niej wszystkimi odpowiednikami. Ale zakładam, że twoja tabela jest UNIQUEwłączona (circle_id, ea_year, ea_month), wtedy porządek sortowania jest deterministyczny i żaden wiersz nie ma rówieśników.

Teraz ORDER BY ... ea_month nie będzie działać z ciągami nazw miesięcy . Postgres będzie sortował alfabetycznie zgodnie z ustawieniami lokalnymi.

Jeśli masz rzeczywiste datewartości zapisane w tabeli, możesz je poprawnie posortować. Jeśli nie, proponuję zastąpić ea_yeari ea_monthjedną kolumną montypu datew Twojej tabeli.

  • Zmień to, co masz to_date():

      to_date(ea_year || ea_month , 'YYYYMonth') AS mon
    
  • Do wyświetlania możesz uzyskać oryginalne ciągi z to_char():

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

Jeśli utkniesz w niefortunnym projekcie, zadziała:

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
źródło
Dzięki za rozwiązanie… Czy możesz mi pomóc w jeszcze jednej rzeczy. Chcę zaimplementować to samo za pomocą kursora, z logiką mówiącą, że każde koło będzie miało tylko jeden rekord przez miesiąc w roku. A funkcja ma uruchamiać się raz w miesiącu. Jak mogę to osiągnąć?
Yousuf Sultan
4
@YousufSultan: W większości przypadków istnieje lepsze rozwiązanie niż kursor. To zdecydowanie kwestia nowego pytania. Rozpocznij nowe pytanie.
Erwin Brandstetter
Uważam, że ta odpowiedź jest niekompletna bez choćby uwagi, że zachodzi tutaj „kadrowanie” range unbounded preceding, które jest domyślnie ustawione na , czyli to samo co range between unbounded preceding and current row. Dlatego, sum()gdy jest używana jako funkcja okna, tworzy bieżącą sumę - podczas gdy inne funkcje okna nie mają tej domyślnej ramki.
Colin 't Hart
1
@ Colin'tHart: Dodałem więcej powyżej, aby wyjaśnić.
Erwin Brandstetter
Oto link do podobnego pytania z prostszym zapytaniem ( PARTITIONnie zawsze jest potrzebne do utworzenia sumy bieżącej): stackoverflow.com/a/5700744/175830
Jason Axelson