DataSuits

Fonctions de fenêtre SQL : classer et cumuler des ventes

Blog
Fonctions de fenêtre SQL : classer et cumuler des ventes
Emoji de visage masculin avec lunettes rondes, clin d'œil et langue tirée sur fond beige.
Emoji 3D d'un homme souriant avec une barbe noire et une coiffure en dreadlocks sur fond violet clair.
Emoji féminin avec peau brune, cheveux tressés noirs, boucles d’oreilles dorées, nez percé, clin d'œil et langue tirée.
Visage animé avec cheveux violets, sourire les yeux fermés et main montrant les doigts croisés.

Candidater à l’une de nos formations DataSuits

Remplissez ce court formulaire pour candidater à la formation de votre choix. Notre équipe pédagogique vous contactera rapidement pour échanger sur votre parcours et vous accompagner dans la suite de votre inscription.

Samy WahbiArticle du Mis à jour le 10 min de lecture

Les fonctions de fenêtre SQL permettent de numéroter des lignes, de classer des ventes ou de calculer un cumul tout en conservant le détail de chaque opération. ROW_NUMBER() attribue un numéro unique dans chaque groupe, RANK() conserve les ex æquo et SUM() OVER (...) calcule une somme sur les lignes choisies.

Ce guide utilise huit ventes entièrement fictives pour expliquer ces trois usages avec PostgreSQL 17. Chaque requête complète contient ses données : vous pouvez la copier dans votre éditeur SQL sans créer de table. L’objectif est de savoir expliquer chaque résultat, notamment lorsque deux montants ou deux dates sont identiques.

À quoi servent les fonctions de fenêtre SQL ?

Elles ajoutent un résultat calculé à chaque ligne. Pour comparer une vente au total de sa région, vous gardez donc son identifiant et son montant. Avec un regroupement classique par région, vous obtenez au contraire une ligne de synthèse par région. Le choix dépend d’abord du niveau de détail attendu dans votre résultat.

SQL est le langage utilisé pour interroger une base de données. Une fonction de fenêtre se reconnaît à OVER, qui définit les lignes prises en compte. Le tutoriel PostgreSQL sur les calculs par fenêtre explique cette conservation des lignes individuelles.

Choisir selon le résultat attendu sur nos huit ventes
BesoinÉcritureRésultat
Un total par régionGROUP BY region avec SUM(montant)2 lignes
Chaque vente et son total régionalSUM(montant) OVER (PARTITION BY region)8 lignes
Chaque vente et le total généralSUM(montant) OVER ()8 lignes

Une partition est le groupe de lignes dans lequel le calcul s’effectue. Ici, PARTITION BY region sépare Nord et Sud. Sans cette clause, toutes les lignes participent à une même partition. Pour approfondir la synthèse par groupe, consultez notre guide du regroupement avec GROUP BY.

Quel jeu de ventes utiliser pour s’entraîner ?

Nous utilisons quatre ventes au Nord et quatre au Sud, datées du 1er au 3 septembre 2026. Chaque ligne représente une vente distincte, avec un identifiant unique et un montant en euros entiers. Les montants et les dates répétés sont volontaires : ils permettent de tester les égalités sans les confondre avec des doublons à supprimer.

Une CTE est un résultat nommé, utilisable dans une requête. Elle commence ici par WITH ventes (...) AS (...). La liste VALUES fournit les données directement, sans fichier à importer ni table persistante à préparer.

Exemple 1 · PostgreSQL
WITH ventes (id, date_vente, region, montant) AS (
  VALUES
    (1, DATE '2026-09-01', 'Nord', 120),
    (2, DATE '2026-09-02', 'Nord', 200),
    (3, DATE '2026-09-02', 'Nord', 200),
    (4, DATE '2026-09-03', 'Nord',  80),
    (5, DATE '2026-09-01', 'Sud',  150),
    (6, DATE '2026-09-02', 'Sud',  300),
    (7, DATE '2026-09-02', 'Sud',  150),
    (8, DATE '2026-09-03', 'Sud',  100)
)
SELECT *
FROM ventes
ORDER BY region, date_vente, id;

  • Nord : 120 + 200 + 200 + 80 = 600 €.
  • Sud : 150 + 300 + 150 + 100 = 700 €.
  • Ensemble : 600 + 700 = 1 300 €.

Ces chiffres sont les résultats de l’exercice fictif, pas des données DataSuits ou client. PostgreSQL documente séparément les listes de valeurs utilisables comme source de données et les requêtes nommées avec WITH. La CTE n’existe que pour la requête qui la suit : chaque exemple ci-dessous la reprend intégralement.

Quelle différence entre ROW_NUMBER et RANK en cas d’égalité ?

ROW_NUMBER() donne un numéro différent à chaque vente dans sa région. RANK() attribue le même rang aux ventes dont les critères de classement sont identiques, puis laisse des numéros vacants. Pour un classement fondé sur le montant, deux ventes à 200 € sont donc premières ex æquo ; la suivante occupe le rang 3.

Dans cet exercice, DESC signifie « du plus grand au plus petit ». Pour numéroter les ventes, nous départageons les montants égaux par leur identifiant croissant. Pour attribuer les rangs, nous conservons uniquement le montant comme critère.

Exemple 2 · PostgreSQL
WITH ventes (id, date_vente, region, montant) AS (
  VALUES
    (1, DATE '2026-09-01', 'Nord', 120),
    (2, DATE '2026-09-02', 'Nord', 200),
    (3, DATE '2026-09-02', 'Nord', 200),
    (4, DATE '2026-09-03', 'Nord',  80),
    (5, DATE '2026-09-01', 'Sud',  150),
    (6, DATE '2026-09-02', 'Sud',  300),
    (7, DATE '2026-09-02', 'Sud',  150),
    (8, DATE '2026-09-03', 'Sud',  100)
)
SELECT region, id, montant,
  ROW_NUMBER() OVER (
    PARTITION BY region
    ORDER BY montant DESC, id
  ) AS numero,
  RANK() OVER (
    PARTITION BY region
    ORDER BY montant DESC
  ) AS rang
FROM ventes
ORDER BY region, montant DESC, id;

Résultat exact : numéros distincts et rangs avec ex æquo
RégionIDMontant (€)numerorang
Nord220011
Nord320021
Nord112033
Nord48044
Sud630011
Sud515022
Sud715032
Sud810044

N’ajoutez pas automatiquement id dans le tri de RANK(). Les identifiants étant uniques, les ventes à 200 € ne seraient plus ex æquo : leurs couples montant-identifiant seraient différents. Ce serait une autre règle de classement.

Le catalogue PostgreSQL des fonctions de classement précise ces comportements. Retenez la décision pratique : numéroter impose de départager ; classer peut nécessiter de conserver les égalités. Ici, nous classons des ventes individuelles, pas des vendeurs ni leurs chiffres d’affaires.

Comment calculer un total régional et un cumul avec SUM OVER ?

Le total régional additionne toutes les ventes d’une région et répète cette somme sur chaque ligne. Le cumul additionne progressivement les ventes dans un ordre choisi. Pour avancer vente par vente, nous utilisons la date puis l’identifiant, et indiquons explicitement que le calcul commence à la première ligne de la partition et s’arrête à la ligne courante.

Exemple 3 · PostgreSQL
WITH ventes (id, date_vente, region, montant) AS (
  VALUES
    (1, DATE '2026-09-01', 'Nord', 120),
    (2, DATE '2026-09-02', 'Nord', 200),
    (3, DATE '2026-09-02', 'Nord', 200),
    (4, DATE '2026-09-03', 'Nord',  80),
    (5, DATE '2026-09-01', 'Sud',  150),
    (6, DATE '2026-09-02', 'Sud',  300),
    (7, DATE '2026-09-02', 'Sud',  150),
    (8, DATE '2026-09-03', 'Sud',  100)
)
SELECT region, id,
  SUM(montant) OVER (
    PARTITION BY region
  ) AS total_region,
  SUM(montant) OVER (
    PARTITION BY region
    ORDER BY date_vente, id
    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
  ) AS cumul
FROM ventes
ORDER BY region, date_vente, id;

Résultat exact : total régional et cumul en euros
RégionIDtotal_regioncumul
Nord1600120
Nord2600320
Nord3600520
Nord4600600
Sud5700150
Sud6700450
Sud7700600
Sud8700700

Le cadre de fenêtre désigne les lignes de la partition utilisées pour calculer la somme à la position actuelle. Lisez les éléments dans cet ordre :

  • PARTITION BY region : le cumul recommence pour chaque région.
  • ORDER BY date_vente, id : les dates avancent, puis l’identifiant départage les ventes du même jour.
  • ROWS : le cadre est défini par des positions de lignes.
  • UNBOUNDED PRECEDING et CURRENT ROW : de la première ligne jusqu’à la ligne actuelle incluse.

L’identifiant rend l’ordre reproductible ; il ne prouve pas l’heure réelle des ventes. Pour suivre leur chronologie précise dans un projet réel, il faudrait un horodatage fiable, puis un identifiant pour les éventuelles égalités.

Pourquoi deux ventes du même jour peuvent-elles avoir le même cumul ?

Avec un tri sur la date seule et sans cadre explicite, PostgreSQL inclut les lignes de même date dans le cumul de chacune. Dans notre région Nord, les deux ventes du 2 septembre affichent alors 520 €. Le moteur calcule conformément à la fenêtre demandée ; c’est la différence entre cette fenêtre et votre intention qui peut provoquer une erreur d’analyse.

Pour observer ce comportement, reprenez la requête précédente et remplacez uniquement l’expression du cumul par SUM(montant) OVER (PARTITION BY region ORDER BY date_vente) AS cumul. Conservez le tri final.

Comparaison vérifiée pour les quatre ventes du Nord, en euros
IDDate seule, cadre par défautDate + ID, cadre ROWS explicite
1120120
2520320
3520520
4600600

La syntaxe officielle des cadres de fenêtre décrit ce défaut : RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. Avec RANGE, la borne courante inclut les lignes ex æquo selon le tri. Avec ROWS, elle s’arrête à la position actuelle.

Pour un cumul vente par vente, choisissez donc un tri unique et un cadre explicite. Ajouter seulement ROWS ne départage pas deux dates identiques. À l’inverse, le tri date-identifiant rendrait déjà les positions uniques avec le cadre par défaut ; écrire ROWS rend ici l’intention plus claire.

Comment garder les deux plus grosses ventes de chaque région ?

Calculez d’abord les numéros, puis filtrez dans une requête extérieure. PostgreSQL évalue les fonctions de fenêtre après le filtre WHERE du même niveau : vous ne pouvez donc pas y utiliser directement leur résultat. Une seconde CTE permet de séparer le calcul du classement et la sélection des lignes à afficher.

Exemple 4 · PostgreSQL
WITH ventes (id, date_vente, region, montant) AS (
  VALUES
    (1, DATE '2026-09-01', 'Nord', 120),
    (2, DATE '2026-09-02', 'Nord', 200),
    (3, DATE '2026-09-02', 'Nord', 200),
    (4, DATE '2026-09-03', 'Nord',  80),
    (5, DATE '2026-09-01', 'Sud',  150),
    (6, DATE '2026-09-02', 'Sud',  300),
    (7, DATE '2026-09-02', 'Sud',  150),
    (8, DATE '2026-09-03', 'Sud',  100)
), classement AS (
  SELECT region, id, montant,
    ROW_NUMBER() OVER (
      PARTITION BY region
      ORDER BY montant DESC, id
    ) AS numero
  FROM ventes
)
SELECT region, id, montant, numero
FROM classement
WHERE numero <= 2
ORDER BY region, numero;

  • Nord : ID 2, 200 €, numéro 1 ; ID 3, 200 €, numéro 2.
  • Sud : ID 6, 300 €, numéro 1 ; ID 5, 150 €, numéro 2.

On obtient exactement deux ventes par région dans ce jeu. La vente 7 est écartée malgré ses 150 €, car la règle choisie favorise l’identifiant le plus petit en cas d’égalité. Cette décision doit être assumée avant de réutiliser la requête.

Pour conserver les ex æquo aux rangs 1 et 2, remplacez ROW_NUMBER() par RANK() et retirez id du tri interne. Le filtre renverra alors deux ventes au Nord et trois au Sud. Ajoutez id au tri final pour stabiliser leur affichage.

Que vérifier avant de réutiliser ces calculs ?

Commencez par définir ce que représente une ligne, puis le groupe de calcul et la règle de tri. Vérifiez ensuite quelques résultats à la main, particulièrement autour des égalités. Un total final correct ne suffit pas à valider les étapes intermédiaires : nos deux méthodes de cumul finissent à 600 € au Nord, mais diffèrent pour la vente 2.

  1. Contrôlez les lignes d’entrée. Une jointure qui multiplie les ventes fausse les sommes. Notre guide des jointures SQL détaille les correspondances entre tables.
  2. Choisissez le moment du filtre. Exclure le 1er septembre avant le calcul retire 120 € du cumul Nord et 150 € du cumul Sud. Filtrer après conserve leur contribution.
  3. Séparez calcul et présentation. Le tri dans OVER organise le calcul ; le ORDER BY final fixe l’ordre affiché, comme le rappelle la documentation PostgreSQL sur le tri des résultats.
  4. Contrôlez les montants absents. SUM ignore les valeurs NULL, c’est-à-dire manquantes. La documentation des fonctions d’agrégation précise aussi qu’une somme sans valeur non nulle renvoie NULL.

Pour vous entraîner, cherchez la première vente où chaque région atteint 500 € de cumul. Calculez le cumul complet, gardez les lignes à 500 € ou plus, puis sélectionnez la première par région dans l’ordre date-identifiant. Réponse attendue : ID 3 au Nord, avec 520 €, et ID 7 au Sud, avec 600 €.

Si ces manipulations sont nouvelles, reprenez les dix requêtes SQL pour débuter l’analyse de données. Pour progresser dans un parcours accompagné, vous pouvez consulter le programme de notre formation SQL. Le bon point de départ reste une requête dont vous savez justifier chaque ligne.

Quelles questions reviennent sur ces calculs SQL ?

Quelle différence entre RANK et DENSE_RANK ?

RANK() laisse des numéros vacants après les ex æquo. DENSE_RANK() enchaîne les rangs distincts sans trou. Pour les montants Nord triés 200, 200, 120, 80, les résultats sont respectivement 1, 1, 3, 4 et 1, 1, 2, 3. Choisissez selon la règle de classement attendue.

Peut-on utiliser OVER sans PARTITION BY ?

Oui. Toutes les lignes retenues au niveau de la requête appartiennent alors à une seule partition. Dans notre exercice, SUM(montant) OVER () affiche 1 300 sur chacune des huit ventes. Ajouter un filtre avant ce calcul modifierait les lignes prises en compte et donc ce total.

ROW_NUMBER supprime-t-il les doublons ?

Non. Il numérote les lignes sans les supprimer. Un filtre extérieur peut ensuite conserver une ligne par groupe, à condition de définir ce groupe et une règle de conservation. Nos deux ventes Nord à 200 € sont des opérations distinctes : leur montant identique ne justifie pas leur suppression.

Peut-on combiner GROUP BY et une fenêtre dans la même requête ?

Oui. Le regroupement intervient avant les calculs de fenêtre, comme l’explique PostgreSQL dans le traitement des fonctions de fenêtre. Vous pouvez produire des totaux journaliers, puis calculer leur cumul. La fenêtre porte alors sur ces journées regroupées, et non plus sur chaque vente.

Financez votre formation en toute simplicité

Le financement ne doit jamais être un frein à votre projet.
Chez DataSuits, nos conseillers pédagogiques vous accompagnent à chaque étape pour trouver la meilleure solution de financement adaptée à votre profil:

logo-mon-compte-professionnel-de-formationFrance-travail-2023.svgopco-1

Candidater à l’une de nos formations DataSuits

Remplissez ce court formulaire pour candidater à la formation de votre choix.
Notre équipe pédagogique vous contactera rapidement pour échanger sur votre parcours et vous accompagner dans la suite de votre inscription.

Logo_datasuits

Votre demande a bien été prise en compte. 📚

Prochaine étape 👇
Réservez un échange téléphonique avec un conseiller pour parler de votre projet de formation
Contacter un conseiller
Oups ! Une erreur est survenue lors de l’envoi du formulaire.