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înOVER (PARTITION BY dept ORDER BY id)atunci când vrei suma întregului departament: prezența luiORDER BYschimbă cadrul ferestrei înRANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW(sumă cumulată, nu totalul partiției!). - Diferența dintre
RANGE(implicit) șiROWSla totalul cumulat: dacă două rânduri au aceeași valoare pe coloana dinORDER BY,RANGEle adună împreună pe ambele rânduri; foloseșteROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROWpentru cumul strict rând cu rând.
Comenzi și funcții înrudite
ROW_NUMBER(), RANK() și DENSE_RANK(): Numerotează și clasifică rândurile în interiorul unei partiții fără a le colapsa ca GROUP BY.LAG() și LEAD(): Accesează valoarea unei coloane de pe rândul anterior (LAG) sau următor (LEAD) fără a face self-join.GROUP BY, HAVING și clauza FILTER: Grupează rândurile pentru agregări și aplică condiții individuale pe fiecare funcție agregată prin FILTER (WHERE …).