Classer dans chaque groupe et lisser une courbe
Avec partition by, une fonction de fenêtre recommence son calcul dans chaque groupe de lignes. Avec un cadre, elle ne regarde que quelques lignes voisines, comme les trois derniers jours.
Un directeur veut les deux meilleurs vendeurs de chaque magasin. Un restaurateur veut une courbe de recettes moins en dents de scie.
À la fin de cette leçon
Remarque
Je peux classer les lignes à l'intérieur de chaque groupe et calculer une moyenne sur une fenêtre glissante.
Mots clés
| Mot | Ce que ça veut dire | Exemple |
|---|---|---|
| Partition | un groupe de lignes qui ont la même valeur | |
| PARTITION BY | découpe la fenêtre en partitions indépendantes | over (partition by magasin) |
| Cadre | les lignes voisines que la fenêtre regarde | |
| ROWS BETWEEN | fixe le début et la fin du cadre, en nombre de lignes | |
| Moyenne mobile | la moyenne des dernières lignes, recalculée à chaque ligne | |
| QUALIFY | filtre les lignes selon le résultat d'une fonction de fenêtre |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
PARTITION BY recommence le calcul par groupe
Sans partition by, la fenêtre est toute la table. Avec partition by magasin, chaque magasin a sa propre fenêtre.
Le rang repart de 1 dans chaque magasin. Chaque ligne reste dans le résultat : on ne regroupe rien.
À vous : un classement par magasin
-- Le classement des vendeurs recommence dans chaque magasin.
select nom, magasin, ventes,
rank() over (partition by magasin order by ventes desc) as rang
from vendeurs
order by magasin, rang;
-- Retirez "partition by magasin" : le classement devient celui de toute la chaîne.
Filtrer le rang pour garder les premiers
On veut les deux meilleurs de chaque magasin. Le rang doit d'abord être calculé, puis filtré.
Placez le calcul dans une sous-requête, et filtrez dehors avec where rang <= 2. DuckDB propose aussi qualify, un filtre posé directement sur la fenêtre.
À vous : les deux meilleurs de chaque magasin
-- Les deux meilleurs vendeurs de chaque magasin.
select nom, magasin, ventes, rang
from (
select nom, magasin, ventes,
row_number() over (partition by magasin order by ventes desc) as rang
from vendeurs
)
where rang <= 2
order by magasin, rang;
-- Changez 2 en 1 pour ne garder que le meilleur de chaque magasin.
À vous : le même filtre avec qualify
-- Le même résultat avec qualify, propre à DuckDB : un filtre sur la fenêtre.
select nom, magasin, ventes,
row_number() over (partition by magasin order by ventes desc) as rang
from vendeurs
qualify rang <= 2
order by magasin, rang;
-- Quelle écriture trouvez-vous la plus facile à relire ?
Une erreur fréquente
Attention
Beaucoup pensent qu'on peut écrire where rang <= 2 dans la requête qui calcule le rang. DuckDB répond : « WHERE clause cannot contain window functions ». Le where agit avant la fenêtre. Utilisez une sous-requête ou qualify.
Un cadre limite la fenêtre aux lignes voisines
Avec order by, la fenêtre va du début jusqu'à la ligne courante. Un cadre la rétrécit.
rows between 2 preceding and current row veut dire : les deux lignes d'avant et la ligne courante. Pour une moyenne, cela donne une moyenne mobile sur trois jours.
Exemple : la moyenne mobile sur trois jours
| Jour | Montant | Lignes du cadre | Calcul | Moyenne |
|---|---|---|---|---|
| 1er septembre | 120 | 1 | 120 / 1 | 120,0 |
| 2 septembre | 150 | 2 | (120 + 150) / 2 | 135,0 |
| 3 septembre | 90 | 3 | (120 + 150 + 90) / 3 | 120,0 |
| 4 septembre | 200 | 3 | (150 + 90 + 200) / 3 | 146,7 |
| 5 septembre | 180 | 3 | (90 + 200 + 180) / 3 | 156,7 |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
À vous : la moyenne mobile
-- La moyenne des trois derniers jours, jour par jour.
select jour, montant,
round(avg(montant) over (order by jour rows between 2 preceding and current row), 1) as moyenne_3j
from recettes
order by jour;
-- Changez 2 preceding en 1 preceding : la moyenne ne porte plus que sur deux jours.
Sans cadre, les ex aequo comptent ensemble
Quand over contient un order by et aucun cadre, DuckDB additionne du début jusqu'à la ligne courante et jusqu'à ses ex aequo.
Avec rows between unbounded preceding and current row, on compte les lignes une par une. Les deux écritures diffèrent seulement quand des valeurs sont égales.
Exemple : deux valeurs égales et deux cadres
| Ligne | Valeur | Cumul sans cadre | Cumul avec ROWS |
|---|---|---|---|
| a | 10 | 20 | 10 |
| b | 10 | 20 | 20 |
| c | 20 | 40 | 40 |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Une autre erreur fréquente
Attention
Beaucoup pensent que la moyenne mobile sur trois jours divise toujours par trois. Les deux premières lignes n'ont pas assez de voisines : le cadre est plus court, et la division aussi.