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
Explorer le jeu de données dans DbGate Lite

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 partition
  • N PRECEDING — N lignes avant la ligne courante
  • CURRENT ROW — la ligne courante
  • N FOLLOWING — N lignes après la ligne courante
  • UNBOUNDED 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

  1. Indexer les colonnes de ORDER BY — les window functions trient les données en interne ; les index aident
  2. Limiter le nombre de partitions — un grand nombre de partitions augmente le coût de calcul
  3. Réutiliser les définitions de fenêtre — de nombreuses bases supportent les clauses WINDOW pour éviter de répéter la même spécification OVER
  4. 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
  5. É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 OVER conservent 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 !