SUM() / AVG() OVER (PARTITION BY) în PostgreSQL

CTE și Window Functions PostgreSQL 8.4+ (FRAMES avansate din PostgreSQL 11+)

Calculează totaluri cumulate (running totals), medii mobile și procente din totalul grupului folosind ferestre.

Sintaxă SQL
SUM(col) OVER (PARTITION BY grup ORDER BY data ROWS BETWEEN n PRECEDING AND CURRENT ROW)

Ce face SUM() / AVG() OVER (PARTITION BY)

Orice funcție agregată standard din PostgreSQL (SUM, AVG, COUNT, MIN, MAX) devine o funcție fereastră atunci când este urmată de clauza OVER (...). Acest lucru îți permite să afișezi pe fiecare rând atât valoarea individuală, cât și totalul sau media întregului grup din care face parte.

Atunci când adaugi ORDER BY în interiorul lui OVER (...), cadrul ferestrei (window frame) se restrânge implicit la rândurile de la începutul partiției până la rândul curent, transformând SUM() într-un total cumulat (running total). Cu ROWS BETWEEN ... poți defini medii mobile pe ultimele N rânduri.

Clauze, operatori și argumente

Clauză / Operator Descriere
SUM(col) OVER () Calculează suma pe toate rândurile returnate de interogare (totalul general).
SUM(col) OVER (PARTITION BY g) Calculează suma întregii partiții g (fără ORDER BY în OVER).
SUM(col) OVER (ORDER BY d) Calculează suma cumulată (running total) de la primul rând până la rândul curent.
ROWS BETWEEN n PRECEDING AND CURRENT ROW Definește o fereastră glisantă de exact n + 1 rânduri fizice (ideală pentru medii mobile).
WINDOW w AS (...) Clauză plasată după HAVING care permite definirea unei ferestre cu nume pentru a nu repeta OVER (...).

Exemple

Exemplul 1: Total cumulat (Running Total) și medie mobilă pe 3 zile

Calculăm soldul cumulat de la începutul lunii și media mobilă a vânzărilor pe ziua curentă plus cele 2 zile anterioare.

SELECT ziua,
       vanzari_lei,
       SUM(vanzari_lei) OVER (
           ORDER BY ziua
           ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
       ) AS total_cumulat,
       ROUND(AVG(vanzari_lei) OVER (
           ORDER BY ziua
           ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
       ), 2) AS medie_mobila_3zile
FROM vanzari_zilnice
ORDER BY ziua;

Rezultat interogare

    ziua    | vanzari_lei | total_cumulat | medie_mobila_3zile 
------------+-------------+---------------+--------------------
 2025-03-01 |     1200.00 |       1200.00 |            1200.00
 2025-03-02 |     1800.00 |       3000.00 |            1500.00
 2025-03-03 |     1500.00 |       4500.00 |            1500.00
 2025-03-04 |     2100.00 |       6600.00 |            1800.00
(4 rows)

Exemplul 2: Ponderea salariului fiecărui angajat în bugetul departamentului său

Folosim SUM(salariu) OVER (PARTITION BY departament) (fără ORDER BY în interiorul lui OVER) pentru a obține totalul departamentului pe fiecare rând.

SELECT nume,
       departament,
       salariu,
       SUM(salariu) OVER (PARTITION BY departament) AS buget_dept,
       ROUND(
           100.0 * salariu / SUM(salariu) OVER (PARTITION BY departament),
           1
       ) AS pondere_proc
FROM angajati
ORDER BY departament, salariu DESC;

Rezultat interogare

     nume      | departament | salariu  | buget_dept | pondere_proc 
---------------+-------------+----------+------------+--------------
 Maria Stan    | Backend     | 16200.00 |   44200.00 |         36.7
 Andrei Popa   | Backend     | 14000.00 |   44200.00 |         31.7
 Victor Toma   | Backend     | 14000.00 |   44200.00 |         31.7
 Vlad Dumitru  | DevOps      | 16200.00 |   29000.00 |         55.9
 Dan Munteanu  | DevOps      | 12800.00 |   29000.00 |         44.1
(5 rows)

Greșeli frecvente

  • Includerea lui ORDER BY în OVER (PARTITION BY dept ORDER BY id) atunci când vrei suma întregului departament: prezența lui ORDER BY schimbă cadrul ferestrei în RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW (sumă cumulată, nu totalul partiției!).
  • Diferența dintre RANGE (implicit) și ROWS la totalul cumulat: dacă două rânduri au aceeași valoare pe coloana din ORDER BY, RANGE le adună împreună pe ambele rânduri; folosește ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW pentru cumul strict rând cu rând.

Comenzi și funcții înrudite