SQL Window Functions : classement, totaux cumulés et analytique
Published: 2026-03-24
Les window functions effectuent des calculs sur un ensemble de lignes liées à la ligne courante — sans réduire le résultat à une seule ligne de sortie comme le fait GROUP BY. Ce sont parmi les fonctionnalités les plus puissantes du SQL moderne, prises en charge dans SQLite (3.25+), PostgreSQL, MySQL (8.0+), SQL Server et Oracle.
Données d’exemple
Nous utilisons deux tables dans tous les exemples :
Table employees :
| id | name | department | hire_date | salary |
|---|---|---|---|---|
| 1 | Alice | Engineering | 2023-01-15 | 95000 |
| 2 | Bob | Engineering | 2023-06-01 | 85000 |
| 3 | Carol | Engineering | 2024-03-10 | 85000 |
| 4 | Dave | Sales | 2023-02-20 | 72000 |
| 5 | Eve | Sales | 2023-09-05 | 68000 |
| 6 | Frank | Sales | 2024-01-15 | 65000 |
| 7 | Grace | Marketing | 2023-04-01 | 82000 |
| 8 | Henry | Marketing | 2024-06-01 | 70000 |
Table monthly_sales :
| id | employee_id | month | revenue |
|---|---|---|---|
| 1 | 1 | 2026-01 | 22000 |
| 2 | 1 | 2026-02 | 25000 |
| 3 | 1 | 2026-03 | 28000 |
| 4 | 2 | 2026-01 | 19000 |
| 5 | 2 | 2026-02 | 21000 |
| 6 | 2 | 2026-03 | 20000 |
| 7 | 3 | 2026-01 | 15000 |
| 8 | 3 | 2026-02 | 17000 |
| 9 | 3 | 2026-03 | 19000 |
| 10 | 4 | 2026-01 | 18000 |
| 11 | 4 | 2026-02 | 22000 |
| 12 | 4 | 2026-03 | 17000 |
| 13 | 5 | 2026-01 | 14000 |
| 14 | 5 | 2026-02 | 16000 |
| 15 | 5 | 2026-03 | 19000 |
| 16 | 6 | 2026-01 | 11000 |
| 17 | 6 | 2026-02 | 13000 |
| 18 | 6 | 2026-03 | 15000 |
| 19 | 7 | 2026-01 | 9000 |
| 20 | 7 | 2026-02 | 12000 |
| 21 | 7 | 2026-03 | 14000 |
| 22 | 8 | 2026-01 | 7000 |
| 23 | 8 | 2026-02 | 9000 |
| 24 | 8 | 2026-03 | 11000 |
Syntaxe des window functions
Chaque window function utilise la clause OVER pour définir sur quelles lignes opérer :
function_name(...) OVER (
[PARTITION BY column]
[ORDER BY column]
[frame_clause]
)
- PARTITION BY — divise les lignes en groupes (comme GROUP BY, mais en conservant toutes les lignes)
- ORDER BY — définit l’ordre des lignes dans chaque partition
- Frame clause — limite la fenêtre à un intervalle de lignes relatif à la ligne courante
ROW_NUMBER()
ROW_NUMBER() attribue un numéro séquentiel unique à chaque ligne dans une partition. La numérotation commence toujours à 1, sans trous ni ex æquo.
Exemple : numéroter tous les employés par salaire
Exemple : numéroter les employés dans chaque département
Avec PARTITION BY, la numérotation repart de 1 pour chaque département :
RANK() et DENSE_RANK()
RANK() et DENSE_RANK() gèrent les ex æquo différemment de ROW_NUMBER() :
- ROW_NUMBER() — toujours unique, départage arbitraire des ex æquo
- RANK() — même valeur → même rang, puis saute des numéros (1, 2, 2, 4)
- DENSE_RANK() — même valeur → même rang, sans trous (1, 2, 2, 3)
Exemple : comparer les trois fonctions de classement
Bob et Carol gagnent tous deux 85000 — observez comment chaque fonction gère l’ex æquo :
Exemple : classer les employés dans chaque département
NTILE()
NTILE(n) répartit les lignes en n groupes (segments) approximativement égaux, numérotés de 1 à n.
Exemple : répartir les employés en quartiles de salaire
Exemple : diviser les employés en deux niveaux par département
LAG() et LEAD()
LAG(column, offset) accède à une valeur d’une ligne précédente. LEAD(column, offset) accède à une valeur d’une ligne suivante. Les deux utilisent par défaut un décalage de 1 et renvoient NULL lorsqu’il n’y a pas de ligne.
Exemple : comparaison du chiffre d’affaires d’un mois sur l’autre
Comparer le chiffre d’affaires de chaque mois avec le mois précédent pour chaque employé :
Exemple : prévisualiser le chiffre d’affaires du mois suivant
Fonctions d’agrégation en fenêtre
Les fonctions d’agrégation standard (SUM, AVG, COUNT, MIN, MAX) peuvent être utilisées comme window functions avec OVER. Contrairement à GROUP BY, chaque ligne est conservée dans le résultat.
Exemple : salaire de chaque employé vs moyenne du département
Exemple : pourcentage du salaire total
Calculer la part de la masse salariale totale représentée par chaque employé :
Exemple : total cumulé et compteur cumulatif
Totaux cumulés par partition
Combinez PARTITION BY et ORDER BY pour calculer des totaux cumulés au sein de groupes.
Exemple : chiffre d’affaires cumulé par employé
Exemple : classement du chiffre d’affaires mensuel tous employés confondus
Trouver le rang de chaque employé pour chaque mois :
Fenêtres (window frames)
Une fenêtre (frame) définit exactement quelles lignes, relatives à la ligne courante, sont incluses dans le calcul. La clause de frame vient après ORDER BY dans OVER.
Syntaxe
ROWS BETWEEN <start> AND <end>
Bornes courantes :
UNBOUNDED PRECEDING— depuis la première ligne de la partitionN PRECEDING— N lignes avant la ligne couranteCURRENT ROW— la ligne couranteN FOLLOWING— N lignes après la ligne couranteUNBOUNDED FOLLOWING— jusqu’à la dernière ligne de la partition
Exemple : moyenne mobile sur 2 mois
Exemple : minimum et maximum cumulés du chiffre d’affaires
FIRST_VALUE et LAST_VALUE
FIRST_VALUE(column) renvoie la première valeur dans la fenêtre. LAST_VALUE(column) renvoie la dernière valeur. Attention : avec la fenêtre par défaut (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW), LAST_VALUE ne voit que jusqu’à la ligne courante.
Exemple : comparer chaque mois au premier mois
Exemple : comparer chaque mois au meilleur mois
Utilisez ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING pour voir toute la partition :
Exemple pratique : meilleur salaire par département
Un schéma courant : utiliser une window function dans une sous-requête, puis filtrer par rang.
Exemple : employé le mieux payé dans chaque département
Exemple : meilleur mois de chiffre d’affaires par employé
Window functions vs. GROUP BY
Différence clé : GROUP BY regroupe les lignes en lignes de synthèse, tandis que les window functions conservent chaque ligne et ajoutent une colonne calculée.
Exemple : comparaison côte à côte
GROUP BY renvoie une ligne par département :
Les window functions conservent toutes les lignes :
Cas d’usage courants des window functions
- Classements et tableaux de bord — ROW_NUMBER, RANK, DENSE_RANK pour les requêtes top-N
- Totaux cumulés — SUM cumulatif pour le suivi financier et des stocks
- Comparaison période à période — LAG/LEAD pour les analyses mois sur mois ou année sur année
- Moyennes mobiles — lissage des données avec AVG basé sur une fenêtre
- Analyse par percentiles — NTILE pour diviser les données en groupes égaux
- Déduplication — ROW_NUMBER pour ne garder qu’une ligne par groupe
- Analyse des écarts — LAG pour détecter les séquences manquantes ou les variations inhabituelles
Conseils de performance
- Indexer les colonnes de ORDER BY — les window functions trient les données en interne ; les index aident
- Limiter le nombre de partitions — un grand nombre de partitions augmente le coût de calcul
- Réutiliser les définitions de fenêtre — de nombreuses bases supportent les clauses
WINDOWpour éviter de répéter la même spécificationOVER - Filtrer après le calcul de fenêtre — les window functions s’exécutent avant
WHERE, utilisez donc des sous-requêtes ou CTE pour filtrer par rang - Éviter les frames inutiles — ne spécifiez des clauses de frame que lorsque vous avez besoin d’un comportement non par défaut
Résumé
- Les window functions calculent des valeurs sur des lignes liées sans réduire le jeu de résultats
- ROW_NUMBER, RANK, DENSE_RANK attribuent des positions ordinales aux lignes
- LAG et LEAD accèdent aux valeurs des lignes précédentes ou suivantes
- NTILE répartit les lignes en segments de taille égale
- FIRST_VALUE et LAST_VALUE renvoient les valeurs aux bornes de la fenêtre
- Les fonctions d’agrégation (SUM, AVG, COUNT, MIN, MAX) avec
OVERconservent toutes les lignes tout en calculant des métriques au niveau du groupe - PARTITION BY crée des groupes indépendants ; ORDER BY définit la séquence des lignes
- Les fenêtres (
ROWS BETWEEN ... AND ...) affinent quelles lignes sont incluses - Utilisez des sous-requêtes pour filtrer selon les résultats de window functions (par ex. top-N par groupe)
- Pris en charge dans SQLite 3.25+, PostgreSQL, MySQL 8.0+, SQL Server et Oracle
Essayez de modifier les exemples pour expérimenter différents partitions, ordres et clauses de fenêtre afin de voir comment les window functions peuvent simplifier vos requêtes analytiques !