Documentation PostgreSQL 14.22 » Tutoriel » Fonctionnalités avancées » Fonctions de fenêtrage

Précédent | Niveau supérieur | Suivant
3.4. Transactions | Sommaire | 3.6. Héritage

## 3.5. Fonctions de fenêtrage

Une fonction de fenêtrage effectue un calcul sur un jeu d'enregistrements liés d'une certaine façon à l'enregistrement courant. On peut les rapprocher des calculs réalisables par une fonction d'agrégat. Cependant, les fonctions de fenêtrage n'entraînent pas le regroupement des enregistrements traités en un seul, comme le ferait l'appel à une fonction d'agrégation standard. À la place, chaque enregistrement garde son identité propre. En coulisse, la fonction de fenêtrage est capable d'accéder à d'autres enregistrements que l'enregistrement courant du résultat de la requête.

Voici un exemple permettant de comparer le salaire d'un employé avec le salaire moyen de sa division :

    SELECT nomdep, noemp, salaire, avg(salaire) OVER (PARTITION BY nomdep) FROM salaireemp;

      nomdep   | noemp | salaire |          avg
    -----------+-------+---------+-----------------------
     develop   |    11 |   5200  | 5020.0000000000000000
     develop   |     7 |   4200  | 5020.0000000000000000
     develop   |     9 |   4500  | 5020.0000000000000000
     develop   |     8 |   6000  | 5020.0000000000000000
     develop   |    10 |   5200  | 5020.0000000000000000
     personnel |     5 |   3500  | 3700.0000000000000000
     personnel |     2 |   3900  | 3700.0000000000000000
     ventes    |     3 |   4800  | 4866.6666666666666667
     ventes    |     1 |   5000  | 4866.6666666666666667
     ventes    |     4 |   4800  | 4866.6666666666666667
    (10 rows)

Les trois premières colonnes viennent directement de la table `salaireemp`, et il y a une ligne de sortie pour chaque ligne de la table. La quatrième colonne représente une moyenne calculée sur tous les enregistrements de la table qui ont la même valeur de `nomdep` que la ligne courante. (Il s'agit effectivement de la même fonction que la fonction d'agrégat classique `avg`, mais la clause `OVER` entraîne son exécution en tant que fonction de fenêtrage et son calcul sur la fenêtre.)

Un appel à une fonction de fenêtrage contient toujours une clause `OVER` qui suit immédiatement le nom et les arguments de la fonction. C'est ce qui permet de la distinguer syntaxiquement d'une fonction simple ou d'une fonction d'agrégat. La clause `OVER` détermine précisément comment les lignes de la requête sont éclatées pour être traitées par la fonction de fenêtrage. La clause `PARTITION BY` contenue dans `OVER` divise les enregistrements en groupes, ou partitions, qui partagent les mêmes valeurs pour la (les) expression(s) contenue(s) dans la clause `PARTITION BY`. Pour chaque enregistrement, la fonction de fenêtrage est calculée sur les enregistrements qui se retrouvent dans la même partition que l'enregistrement courant.

Vous pouvez aussi contrôler l'ordre dans lequel les lignes sont traitées par les fonctions de fenêtrage en utilisant la clause `ORDER BY` à l'intérieur de la clause `OVER` (la partition traitée par le `ORDER BY` n'a de plus pas besoin de correspondre à l'ordre dans lequel les lignes seront affichées). Voici un exemple :

    SELECT nomdep, noemp, salaire,
           rank() OVER (PARTITION BY nomdep ORDER BY salaire DESC)
    FROM salaireemp;

      nomdep   | noemp | salaire| rank
    -----------+-------+--------+------
     develop   |     8 |   6000 |    1
     develop   |    10 |   5200 |    2
     develop   |    11 |   5200 |    2
     develop   |     9 |   4500 |    4
     develop   |     7 |   4200 |    5
     personnel |     2 |   3900 |    1
     personnel |     5 |   3500 |    2
     ventes    |     1 |   5000 |    1
     ventes    |     4 |   4800 |    2
     ventes    |     3 |   4800 |    2
    (10 rows)

On remarque que la fonction `rank` produit un rang numérique pour chaque valeur `ORDER BY` distincte dans la partition de la ligne courante, en utilisant l'ordre défini par la clause `ORDER BY`. `rank` n'a pas besoin de paramètre explicite, puisque son comportement est entièrement déterminé par la clause `OVER`.

Les lignes prises en compte par une fonction de fenêtrage sont celles de la table virtuelle produite par la clause `FROM` de la requête filtrée par ses clauses `WHERE`, `GROUP BY` et `HAVING`, s'il y en a. Par exemple, une ligne rejetée parce qu'elle ne satisfait pas à la condition `WHERE` n'est vue par aucune fonction de fenêtrage. Une requête peut contenir plusieurs de ces fonctions de fenêtrage qui découpent les données de façons différentes, par le biais de clauses `OVER` différentes, mais elles travaillent toutes sur le même jeu d'enregistrements, défini par cette table virtuelle.

`ORDER BY` peut être omis lorsque l'ordre des enregistrements est sans importance. Il est aussi possible d'omettre `PARTITION BY`, auquel cas il n'y a qu'une seule partition, contenant tous les enregistrements.

Il y a un autre concept important associé aux fonctions de fenêtrage : pour chaque enregistrement, il existe un jeu d'enregistrements dans sa partition appelé son window frame (cadre de fenêtre). Certaines fonctions de fenêtrage travaillent uniquement sur les enregistrements du window frame, plutôt que sur l'ensemble de la partition. Par défaut, si on a précisé une clause `ORDER BY`, la window frame contient tous les enregistrements du début de la partition jusqu'à l'enregistrement courant, ainsi que tous les enregistrements suivants qui sont égaux à l'enregistrement courant au sens de la clause `ORDER BY`. Quand `ORDER BY` est omis, la window frame par défaut contient tous les enregistrements de la partition. [5] Voici un exemple utilisant `sum` :

    SELECT salaire, sum(salaire) OVER () FROM salaireemp;

     salaire|  sum
    --------+-------
       5200 | 47100
       5000 | 47100
       3500 | 47100
       4800 | 47100
       3900 | 47100
       4200 | 47100
       4500 | 47100
       4800 | 47100
       6000 | 47100
       5200 | 47100
    (10 rows)

Dans l'exemple ci-dessus, puisqu'il n'y a pas d'`ORDER BY` dans la clause `OVER`, la window frame est égale à la partition ; en d'autres termes, chaque somme est calculée sur toute la table, ce qui fait qu'on a le même résultat pour chaque ligne du résultat. Mais si on ajoute une clause `ORDER BY`, on a un résultat très différent :

    SELECT salaire, sum(salaire) OVER (ORDER BY salaire) FROM salaireemp;

     salaire|  sum
    --------+-------
       3500 |  3500
       3900 |  7400
       4200 | 11600
       4500 | 16100
       4800 | 25700
       4800 | 25700
       5000 | 30700
       5200 | 41100
       5200 | 41100
       6000 | 47100
    (10 rows)

Ici, sum est calculé à partir du premier salaire (c'est-à-dire le plus bas) jusqu'au salaire courant, en incluant tous les doublons du salaire courant (remarquez les valeurs pour les salaires identiques).

Les fonctions window ne sont autorisées que dans la liste `SELECT` et la clause `ORDER BY` de la requête. Elles sont interdites ailleurs, comme dans les clauses `GROUP BY`,`HAVING` et `WHERE`. La raison en est qu'elles sont exécutées après le traitement de ces clauses. Par ailleurs, les fonctions de fenêtrage s'exécutent après les fonctions d'agrégat classiques. Cela signifie qu'il est permis d'inclure une fonction d'agrégat dans les arguments d'une fonction de fenêtrage, mais pas l'inverse.

S'il y a besoin de filtrer ou de grouper les enregistrements après le calcul des fonctions de fenêtrage, une sous-requête peut être utilisée. Par exemple :

    SELECT nomdep, noemp, salaire, date_embauche
    FROM
      (SELECT nomdep, noemp, salaire, date_embauche,
              rank() OVER (PARTITION BY nomdep ORDER BY salaire DESC, noemp) AS pos
         FROM salaireemp
      ) AS ss
    WHERE pos < 3;

La requête ci-dessus n'affiche que les enregistrements de la requête interne ayant un rang inférieur à 3.

Quand une requête met en jeu plusieurs fonctions de fenêtrage, il est possible d'écrire chacune avec une clause `OVER` différente, mais cela entraîne des duplications de code et augmente les risques d'erreurs si on souhaite le même comportement pour plusieurs fonctions de fenêtrage. À la place, chaque comportement de fenêtrage peut être associé à un nom dans une clause `WINDOW` et ensuite être référencé dans `OVER`. Par exemple :

    SELECT sum(salaire) OVER w, avg(salaire) OVER w
      FROM salaireemp
      WINDOW w AS (PARTITION BY nomdep ORDER BY salaire DESC);

Plus de détails sur les fonctions de fenêtrage sont disponibles dans la Section 4.2.8, la Section 9.22, la Section 7.2.5 et la page de référence SELECT.

[5] Il existe des options pour définir la window frame autrement, mais ce tutoriel ne les présente pas. Voir la Section 4.2.8 pour les détails.