Ce face ROW_NUMBER(), RANK() și DENSE_RANK()
Funcțiile fereastră (Window Functions) efectuează calcule peste o mulțime de rânduri înrudite cu rândul curent (definite de clauza OVER (...)), dar, spre deosebire de GROUP BY, păstrează fiecare rând individual în rezultat.
Cele trei funcții de clasament gestionează diferit egalitățile (rândurile cu aceeași valoare pe coloana din ORDER BY): ROW_NUMBER() dă fiecărui rând un număr unic consecutiv (1, 2, 3, 4); RANK() dă același loc la egalitate și lasă goluri după (1, 2, 2, 4); iar DENSE_RANK() dă același loc la egalitate fără să sară peste numere (1, 2, 2, 3).
Clauze, operatori și argumente
| Clauză / Operator | Descriere |
|---|---|
ROW_NUMBER() |
Atribuie un număr secvențial unic (1, 2, 3…) fiecărui rând din partiție. |
RANK() |
Clasament olimpic cu goluri la egalități (ex: 1, 2, 2, 4). |
DENSE_RANK() |
Clasament compact fără goluri după egalități (ex: 1, 2, 2, 3). |
OVER (PARTITION BY ...) |
Împarte rândurile în grupuri independente (partiții); numerotarea reîncepe de la 1 pentru fiecare partiție. |
OVER (ORDER BY ...) |
Definește ordinea în care sunt numerotate și clasificate rândurile în cadrul fiecărei partiții. |
Exemple
Exemplul 1: Comparația directă între ROW_NUMBER(), RANK() și DENSE_RANK()
Clasificăm angajații din fiecare departament în funcție de salariu (observă comportamentul la salariul egal 14000.00).
SELECT departament,
nume,
salariu,
ROW_NUMBER() OVER (PARTITION BY departament ORDER BY salariu DESC) AS nr_rand,
RANK() OVER (PARTITION BY departament ORDER BY salariu DESC) AS loc_rank,
DENSE_RANK() OVER (PARTITION BY departament ORDER BY salariu DESC) AS loc_dense
FROM angajati
WHERE departament = 'Backend';
Rezultat interogare
departament | nume | salariu | nr_rand | loc_rank | loc_dense
-------------+---------------+----------+---------+----------+-----------
Backend | Maria Stan | 16200.00 | 1 | 1 | 1
Backend | Andrei Popa | 14000.00 | 2 | 2 | 2
Backend | Victor Toma | 14000.00 | 3 | 2 | 2
Backend | Elena Lazăr | 11500.00 | 4 | 4 | 3
(4 rows)
Exemplul 2: Eliminarea duplicatelor păstrând cel mai recent rând cu ROW_NUMBER()
Identificăm și selectăm doar primele 2 produse cel mai bine vândute din fiecare categorie folosind un CTE și ROW_NUMBER().
WITH clasament AS (
SELECT categorie,
denumire,
unitati_vandute,
ROW_NUMBER() OVER (
PARTITION BY categorie
ORDER BY unitati_vandute DESC, id ASC
) AS pozitie
FROM produse
)
SELECT categorie, pozitie, denumire, unitati_vandute
FROM clasament
WHERE pozitie <= 2;
Rezultat interogare
categorie | pozitie | denumire | unitati_vandute
-----------+---------+-------------------------+-----------------
Laptopuri | 1 | Laptop Office 15 | 340
Laptopuri | 2 | ProBook Ultra 16 M3 | 215
Monitoare | 1 | Monitor IPS 27 inch 4K | 510
Monitoare | 2 | Dell UltraSharp 32 4K | 190
(4 rows)
Greșeli frecvente
- Încercarea de a filtra o funcție fereastră direct în
WHERE(ex:WHERE ROW_NUMBER() OVER (...) = 1): funcțiile fereastră se evaluează dupăWHEREșiGROUP BY, deci trebuie încapsulate într-un CTE (WITH) sau o subinterogare. - Pentru „Top 1 pe fiecare grup”,
SELECT DISTINCT ONeste adesea mai concis și mai rapid în PostgreSQL decâtROW_NUMBER() = 1.
Comenzi și funcții înrudite
SELECT și DISTINCT ON: Extrage rânduri din tabele și păstrează doar primul rând pentru fiecare grup de valori distincte.LAG() și LEAD(): Accesează valoarea unei coloane de pe rândul anterior (LAG) sau următor (LEAD) fără a face self-join.SUM() / AVG() OVER (PARTITION BY): Calculează totaluri cumulate (running totals), medii mobile și procente din totalul grupului folosind ferestre.