Les fonctions OLAP et la clause WINDOW de fenêtrage

SQLPro

SQL:2003 a introduit la notion de fonctions OLAP (appelées couramment fonctions fenêtrées, en anglais windowing functions, débattues lors de la norme SQL:1999 et finalement adoptées dans la norme SQL:2003) et dans ce concept se regroupent deux types de fonctions bien distinctes opérant sur des ensembles de données spécifiques appelés « fenêtre de données », constitués par des sous-ensembles des données du résultat de la requête globale.

- Titre : Les fonctions OLAP et la clause WINDOW de fenêtrage

- Auteur : SQLPro

- Parution : 10 novembre 2009

- Mise à jour : 12 novembre 2009

- Licence : Copyright ® 2009 Frédéric Brouard. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Les fonctions OLAP et la clause WINDOW de fenêtrage

- II. Catégories de fonctions fenêtrées

- III. Syntaxe des appels aux fonctions fenêtrées

- III-A. Syntaxe simplifiée d'un appel à une fonction fenêtrée

- III-B. Filtrer le résultat d'une fonction fenêtrée

- III-C. Syntaxe avancée d'un appel à une fonction fenêtrée

- IV. PERCENTILE… et WITHIN GROUP

- V. Utilisation de la clause WINDOW

- VI. Position des NULL

- VII. Notes techniques

- VIII. ANNEXE A - liste des fonctions d'agrégation

- IX. ANNEXE B - liste des fonctions de classement

- X. ANNEXE C - position des éditeurs sur les fonctions fenêtrées

- XI. ANNEXE D - informations complémentaires

- XII. ANNEXE E - fichier SQL pour les exemples

I - Les fonctions OLAP et la clause WINDOW de fenêtrage

On peut symboliser cette vision des principes du fenêtrage par l'image suivante :

figure 1 : fenêtres de données

La partie en jaune figure l'ensemble des résultats d'une requête. Au sein de ces résultats, on a taillé deux fenêtres sur lesquelles portent certains calculs. La fenêtre verte incorpore elle-même des sous-fenêtres. La fenêtre orange prend en compte la totalité des données.

NOTA : certains auteurs les appellent « fonctions analytiques », ce qui est faux, car seule une partie de ces fonctions est réellement classable dans cette catégorie. D'autres les appellent fonctions OLAP, ce qui est aussi faux, car la norme considère une classe particulière de fonctions OLAP utilisant les agrégats avec une clause de groupage WITH CUBE ou WITH ROLLUP.

II - Catégories de fonctions fenêtrées

Il existe deux catégories de fonctions OLAP.

La première est appelée fonctions analytiques et concerne des fonctions bien connues de SQL, les fonctions d'agrégation telles que SUM, COUNT, MAX, MIN, AVG ou encore EVERY, ANY (ou SOME), STDDEV_POP, STDDEV_SAMP, VAR_POP, VAR_SAMP, PERCENTILE_CONT, PERCENTILE_DISC (voir annexe A).

Ces fonctions sont purement relationnelles, dans le sens où elles obéissent à une logique ensembliste. Elles ont besoin d'opérer des calculs directement sur les données de la requête.

La seconde est appelée fonctions de classement et permet d'enrichir un résultat par un classement reposant sur un ordre défini dans l'application de la fonction. Ce sont les fonctions RANK, DENSE_RANK, PERCENT_RANK, CUME_DIST, ROW_NUMBER, NTILE, LEAD, LAG, FIRST_VALUE, LAST_VALUE, NTH_VALUE (voir annexe B).

Ces fonctions ne sont pas relationnelles, car elles supposent l'application d'un ordre dans leur traitement ce qui est contraire au principe ensembliste des bases de données relationnelles.

Ceci a deux conséquences : la difficulté de faire un traitement en parallèle et l'indéterminisme, c'est-à-dire la possibilité qu'une même exécution de la requête avec le même jeu de données donne des résultats différents…

Notez aussi que, contrairement aux fonctions analytiques, elles n'effectuent aucun calcul sur les données de la requête. Elles se contentent de comparer des données pour effectuer un classement.

III - Syntaxe des appels aux fonctions fenêtrées

Comme la syntaxe globale de ce type de fonction est assez complexe, nous allons la présenter en deux temps…

III-A - Syntaxe simplifiée d'un appel à une fonction fenêtrée

La syntaxe simplifiée d'un appel à une fonction fenêtrée est la suivante :

<fonction_fenêtrée> OVER <fenêtre_de_données>

<fenêtre_de_données> ::=
OVER ( [ PARTITION BY ( <liste_expression_groupage> ) ]
[ ORDER BY <liste_expression_ordonancement> ] )

PARTITION BY : permet d'organiser le jeu de résultats en groupes logiques, d'après les différentes valeurs de l'expression spécifiée. Cela correspond à peu de choses près à la clause GROUP BY lorsque vous utilisez des agrégats directement dans une requête.

En cas d'omission de cette clause, l'intégralité du jeu de résultats est considérée comme une partition.

ORDER BY : définit le tri des lignes dans chaque partition d'un jeu de résultats. Les mots clefs ASC et DESC sont utilisables pour définir le sens du tri, ainsi qu'une clause COLLATE dans le cas des littéraux. Enfin la précision NULL FIRT ou NULL LAST peut être apportée pour situer le classement des NULL en début ou en fin de résultat.

Pour certains cas d'utilisation des fonctions, cette clause doit être omise (en particulier pour les fonctions d'agrégation).

NOTA : le contenu de OVER peut être vide (par exemple pour avoir en ligne le maximum absolu).

Exemple 1 (voir l'annexe E pour les jeux d'essais) - agrégat simple

À partir des données de la table des films (T_FILM_FLM) nous aimerions, en sus de toutes les données, faire figurer le nombre de films que chaque metteur en scène a réalisés.

Pour cela nous devons rajouter un comptage avec comme niveau de rupture le nom du réalisateur. Ceci se fait à l'aide de la fonction COUNT(*) appliquée sur (OVER) la partition (PARTITION BY) FLM_REALISATEUR (colonne), comme ceci :

SELECT *,
COUNT(*) OVER(PARTITION BY FLM_REALISATEUR) AS NB_FILM_REAL
FROM   T_FILM_FLM
ORDER  BY FLM_REALISATEUR

FLM_ID  FLM_TITRE                          FLM_REALISATEUR    FLM_DUREE_MN FLM_ANNEE FLM_GENRE        NB_FILM_REAL
------- ---------------------------------- ------------------ ------------ --------- ---------------- ------------
8       La mort aux trousses               Alfred Hitchcock   136          1959      Policier         4
15      Fenêtre sur cour                   Alfred Hitchcock   112          1954      Policier         4
19      Les oiseaux                        Alfred Hitchcock   130          1963      Fantastique      4
23      Psychose                           Alfred Hitchcock   109          1960      Fantastique      4
24      Boulevard du crépuscule            Billy Wilder       110          1950      Drame            5
21      Assurance sur la mort              Billy Wilder       107          1944      Policier         5
27      Témoin à charge                    Billy Wilder       116          1958      Policier         5
30      La vie privée de sherlock holmes   Billy Wilder       125          1970      Policier         5
37      Certains l'aiment chaud            Billy Wilder       120          1959      Comédie          5
35      La panthère rose                   Blake Edwards      113          1963      Comédie          4
28      Diamants sur canapé                Blake Edwards      115          1961      Comédie          4
14      Victor, Victoria                   Blake Edwards      132          1982      Comédie musicale 4
10      La party                           Blake Edwards      99           1968      Comédie musicale 4
2       L'arnaque                          George Roy Hill    129          1973      Policier         5
29      Bucth Cassidy et le kid            George Roy Hill    110          1969      Western          5
32      Abattoir 5                         George Roy Hill    104          1972      Science fiction  5
34      La kermesse des aigles             George Roy Hill    107          1975      Historique       5
38      Le monde selon Garp                George Roy Hill    136          1983      Science fiction  5
39      Eve                                Joseph Mankiewicz  130          1950      Drame            4
3       Le limier                          Joseph Mankiewicz  138          1972      Policier         4
6       Jules Cesar                        Joseph Mankiewicz  120          1953      Histoire         4
13      L'aventure de madame Muir          Joseph Mankiewicz  104          1947      Fantastique      4
16      La soif du mal                     Orson Welles       108          1958      Policier         4
12      Le procès                          Orson Welles       130          1962                       4
9       La dame de Shangai                 Orson Welles       90           1946      Policier         4
4       Citizen Kane                       Orson Welles       115          1941      Biographie       4
5       Le coup de l'escalier              Robert Wise        95           1959      Policier         7
7       Le mystère androméde               Robert Wise        115          1971      Science fiction  7
1       West Side Story                    Robert Wise        145          1961      Comédie musicale 7
11      Audrey Rose                        Robert Wise        113          1977      Fantastique      7
18      Star Trek                          Robert Wise        129          1979      Science fiction  7
25      La mélodie du bonheur              Robert Wise        166          1965      Comédie musicale 7
36      La maison du diable                Robert Wise        112          1963      Fantastique      7
40      Dr. Folamour                       Stanley Kubrick    93           1963      Science fiction  7
26      Shinnig                            Stanley Kubrick    120          1980      Fantastique      7
22      Orange mécanique                   Stanley Kubrick    136          1971      Drame            7
20      Barry Lyndon                       Stanley Kubrick    187          1975      Biographie       7
33      2001 l'odyssée de l'espace         Stanley Kubrick    141          1968      Science fiction  7
31      Lolita                             Stanley Kubrick    153          1962      Drame            7
17      Les sentiers de la gloire          Stanley Kubrick    88           1957      Histoire         7

Notez combien le résultat de cette colonne est répétitif : il figure autant de fois qu'il y a de lignes portant le nom du réalisateur.

Si nous devions représenter symboliquement le fonctionnement de cette requête, l'image serait alors la suivante :

figure 2 : fenêtres avec partition

Si nous n'avions pas de fonction fenêtrée, cette requête s'écrirait :

SELECT *,
(SELECT COUNT(*)
FROM   T_FILM_FLM AS Tin
WHERE  Tin.FLM_REALISATEUR
= Tout.FLM_REALISATEUR) AS NOMBRE_FILM_DU_REALISATEUR
FROM   T_FILM_FLM  AS Tout
ORDER  BY FLM_REALISATEUR

À ce stade il est intéressant de constater que les plans de requêtes de ces deux requêtes montrent que l'avantage est à celle utilisant la fonction fenêtrée :

figure 3 : plan de requêtes dans Microsoft SQL Server

Cette dernière consommant 50 % de ressources supplémentaires !

Exemple 2 (voir l'annexe E pour les jeux d'essais) - agrégats simples

Nous voulons maintenant connaître l'année du plus vieux film et l'année du film le plus récent pour chaque réalisateur, ainsi que le classement chronologique des films pour le réalisateur et globalement (quels que soient les réalisateurs).

Pour connaître l'année du plus vieux film du réalisateur, il faut calculer le minimum (MIN) de l'année (colonne FLM_ANNEE) appliqué sur (OVER) la partition (PARTITION BY), c'est-à-dire par groupe sur le réalisateur (colonne FLM_REALISATEUR).

Pour l'année du film le plus récent par réalisateur, il suffit d'utiliser la fonction d'agrégation MAX avec les mêmes éléments.

Pour donner le classement absolu par ancienneté des films, il suffit d'invoquer l'une des fonctions de classement (RANK, DENSE_RANK, PERCENT_RANK) appliquée sur (OVER) le tri (ORDER BY) effectué sur la colonne FLM_ANNEE.

Pour donner le classement par ancienneté des films, relatif au réalisateur, il suffit d'invoquer l'une des fonctions de classement (RANK, DENSE_RANK, PERCENT_RANK) appliquée sur (OVER) la partition ou sous-groupe (PARTITION BY) constitué par la colonne FM_REALISATEUR et le tri (ORDER BY) effectué sur la colonne FLM_ANNEE.

Tout ceci se résume dans la requête suivante :

SELECT FLM_TITRE, FLM_REALISATEUR,
MIN(FLM_ANNEE) OVER(PARTITION BY FLM_REALISATEUR)
AS AN_PLUS_VIEUX_FILM_REAL,
MAX(FLM_ANNEE) OVER(PARTITION BY FLM_REALISATEUR)
AS AN_PLUS_RECENT_FILM_REAL,
RANK() OVER(ORDER BY FLM_ANNEE)
AS CHRONO_FILM,
RANK() OVER(PARTITION BY FLM_REALISATEUR ORDER BY FLM_ANNEE)
AS CHRONO_FILM_REAL
FROM   T_FILM_FLM
ORDER  BY FLM_REALISATEUR

FLM_TITRE                          FLM_REALISATEUR     AN_PLUS_VIEUX  AN_PLUS_RECENT  CHRONO  CHRONO
_FILM_REAL     _FILM_REAL      _FILM   _FILM_REAL
---------------------------------- ------------------- -------------- --------------- ------- ----------
Fenêtre sur cour                   Alfred Hitchcock    1954           1963            8       1
La mort aux trousses               Alfred Hitchcock    1954           1963            12      2
Psychose                           Alfred Hitchcock    1954           1963            15      3
Les oiseaux                        Alfred Hitchcock    1954           1963            20      4
Assurance sur la mort              Billy Wilder        1944           1970            2       1
Boulevard du crépuscule            Billy Wilder        1944           1970            5       2
Témoin à charge                    Billy Wilder        1944           1970            10      3
Certains l'aiment chaud            Billy Wilder        1944           1970            12      4
La vie privée de sherlock holmes   Billy Wilder        1944           1970            28      5
Diamants sur canapé                Blake Edwards       1961           1982            16      1
La panthère rose                   Blake Edwards       1961           1982            20      2
La party                           Blake Edwards       1961           1982            25      3
Victor, Victoria                   Blake Edwards       1961           1982            39      4
Bucth Cassidy et le kid            George Roy Hill     1969           1983            27      1
Abattoir 5                         George Roy Hill     1969           1983            31      2
L'arnaque                          George Roy Hill     1969           1983            33      3
La kermesse des aigles             George Roy Hill     1969           1983            34      4
Le monde selon Garp                George Roy Hill     1969           1983            40      5
L'aventure de madame Muir          Joseph Mankiewicz   1947           1972            4       1
Eve                                Joseph Mankiewicz   1947           1972            5       2
Jules Cesar                        Joseph Mankiewicz   1947           1972            7       3
Le limier                          Joseph Mankiewicz   1947           1972            31      4
Citizen Kane                       Orson Welles        1941           1962            1       1
La dame de Shangai                 Orson Welles        1941           1962            3       2
La soif du mal                     Orson Welles        1941           1962            10      3
Le procès                          Orson Welles        1941           1962            18      4
Le coup de l'escalier              Robert Wise         1959           1979            12      1
West Side Story                    Robert Wise         1959           1979            16      2
La maison du diable                Robert Wise         1959           1979            20      3
La mélodie du bonheur              Robert Wise         1959           1979            24      4
Le mystère androméde               Robert Wise         1959           1979            29      5
Audrey Rose                        Robert Wise         1959           1979            36      6
Star Trek                          Robert Wise         1959           1979            37      7
Les sentiers de la gloire          Stanley Kubrick     1957           1980            9       1
Lolita                             Stanley Kubrick     1957           1980            18      2
Dr. Folamour                       Stanley Kubrick     1957           1980            20      3
2001 l'odyssée de l'espace         Stanley Kubrick     1957           1980            25      4
Orange mécanique                   Stanley Kubrick     1957           1980            29      5
Barry Lyndon                       Stanley Kubrick     1957           1980            34      6
Shinnig                            Stanley Kubrick     1957           1980            38      7

Notez que dans les résultats de classement absolu par ancienneté des films, certains ayant été produits la même année sont ex aequo (par exemple « Les oiseaux », « La panthère rose », « La maison du diable », « Dr. Folamour » : classés 20e) ce qui fait qu'au rang suivant on passe à 24. Il n'y a donc, ni 21e, ni 22e, ni 23e film(s). On aurait pu éviter ces « trous » en utilisant la fonction DENSE_RANK.

Bien entendu il est possible de se passer des fonctions fenêtrées. Pour les fonctions d'agrégation, cela se fait en général par une sous-requête corrélée présente dans la clause SELECT. Pour les fonctions de classement, les choses sont plus complexes. Il faut en général faire appel à un comptage sur une inégalité.

SELECT FLM_TITRE, FLM_REALISATEUR,
(SELECT MIN(FLM_ANNEE)
FROM   T_FILM_FLM  AS Tin
WHERE  Tout.FLM_REALISATEUR = Tin.FLM_REALISATEUR),
(SELECT MAX(FLM_ANNEE)
FROM   T_FILM_FLM  AS Tin
WHERE  Tout.FLM_REALISATEUR = Tin.FLM_REALISATEUR),
(SELECT COUNT(*)
FROM   T_FILM_FLM AS Tin
WHERE  Tout.FLM_ANNEE > Tin.FLM_ANNEE) + 1,
(SELECT COUNT(*)
FROM   T_FILM_FLM AS Tin
WHERE  Tout.FLM_ANNEE > Tin.FLM_ANNEE
AND  Tin.FLM_REALISATEUR = Tout.FLM_REALISATEUR) + 1
FROM   T_FILM_FLM AS Tout
ORDER  BY FLM_REALISATEUR

Exemple 3 (voir l'annexe E pour les jeux d'essais) - agrégat d'agrégat

On peut aussi à l'aide des fonctions fenêtrées, réaliser plus facilement un classement d'agrégat, ou encore un agrégat d'agrégats. Par exemple nous pouvons obtenir par réalisateur la somme des durées de leurs films (durée totale des films), mais aussi faire figurer le maximum de cette somme, autrement dit la durée la plus longue si l'on visionnait les films d'un réalisateur mis bout à bout :

SELECT FLM_REALISATEUR, SUM(FLM_DUREE_MN) AS DUREE_TOTALE,
RANK() OVER(ORDER BY SUM(FLM_DUREE_MN)) AS RANG_DUREE_TOTALE,
MAX(SUM(FLM_DUREE_MN)) OVER() AS DUREE_TOTALE_MAX
FROM   T_FILM_FLM
GROUP  BY FLM_REALISATEUR
ORDER  BY FLM_REALISATEUR

FLM_REALISATEUR        DUREE_TOTALE RANG_DUREE_TOTALE    DUREE_TOTALE_MAX
---------------------- ------------ -------------------- ----------------
Alfred Hitchcock       487          3                    918
Billy Wilder           578          5                    918
Blake Edwards          459          2                    918
George Roy Hill        586          6                    918
Joseph Mankiewicz      492          4                    918
Orson Welles           443          1                    918
Robert Wise            875          7                    918
Stanley Kubrick        918          8                    918

À nouveau la comparaison des plans de requêtes ne laisse aucun doute sur la requête la plus consommatrice :

comparaisons des plans de requêtes sous PostGreSQL :

figure 4 : plan de requête dans PostGreSQL

avec un cout de 126,75 pour les fonctions fenêtrées

figure 5 : plan de requête dans PostGreSQL

avec un cout de 40 212,54 sans les fonctions fenêtrées.

III-B - Filtrer le résultat d'une fonction fenêtrée

Vous ne pouvez pas filtrer directement le résultat d'une fonction fenêtrée.

En effet, le filtre WHERE ne porte que sur les données contenues dans les tables. Or le résultat d'une fonction fenêtrée qu'elle soit un classement ou une agrégation, n'est pas connu dans les données.

Le filtre HAVING ne peut non plus être utilisé, car ces fonctions opèrent après que le résultat de la requête a été produit. En effet, rien n'empêche de faire porter une fonction fenêtrée sur un agrégat.

Le seul moyen de filtrer le résultat d'une fonction fenêtrée est d'utiliser une table dérivée, soit directement dans la requête, soit par le biais d'une CTE ou encore à l'aide d'une vue.

Exemples 4 (voir l'annexe E pour les jeux d'essais) - filtrage d'un agrégat (pagination)

Vous souhaitez paginer les résultats d'une requête afin de présenter quelques lignes de chaque page d'un site web par exemple. Deux méthodes peuvent être envisagées :

- paginer en un nombre fixe de lignes par page, quel que soit le nombre de pages ;

- paginer en un nombre fixe de pages, quel que soit le nombre de lignes par page.

Dans le premier cas, vous devez utiliser la fonction de classement.

Pagination par ligne des films dans l'ordre chronologique du plus récent au plus ancien, avec 5 lignes par page, appel de la page 2 :

SELECT FLM_TITRE, FLM_REALISATEUR, FLM_ANNEE
FROM   (SELECT FLM_TITRE, FLM_REALISATEUR, FLM_ANNEE,
ROW_NUMBER() OVER(ORDER BY FLM_ANNEE DESC) - 1 AS N
FROM   T_FILM_FLM) AS T
WHERE  (N / 5) + 1 = 2

Tout se joue dans le WHERE.

Ce genre de requête gagne à être écrit à l'aide d'une CTE pour la rendre plus lisible :

WITH
T AS (SELECT FLM_TITRE, FLM_REALISATEUR, FLM_ANNEE,
ROW_NUMBER() OVER(ORDER BY FLM_ANNEE DESC) - 1 AS N
FROM   T_FILM_FLM)
SELECT FLM_TITRE, FLM_REALISATEUR, FLM_ANNEE
FROM   T
WHERE  (N / 5) + 1 = 2

FLM_TITRE                   FLM_REALISATEUR        FLM_ANNEE
--------------------------- ---------------------- ---------
Barry Lyndon                Stanley Kubrick        1975
La kermesse des aigles      George Roy Hill        1975
L'arnaque                   George Roy Hill        1973
Le limier                   Joseph Mankiewicz      1972
Abattoir 5                  George Roy Hill        1972

Pagination par page des films dans l'ordre chronologique du plus récent au plus ancien, avec 5 pages, appel de la page 2 :

SELECT FLM_TITRE, FLM_REALISATEUR, FLM_ANNEE
FROM   (SELECT FLM_TITRE, FLM_REALISATEUR, FLM_ANNEE,
NTILE(5) OVER(ORDER BY FLM_ANNEE DESC) AS N
FROM   T_FILM_FLM) AS T
WHERE  N = 2

FLM_TITRE                           FLM_REALISATEUR      FLM_ANNEE
----------------------------------- -------------------- ---------
Le limier                           Joseph Mankiewicz    1972
Abattoir 5                          George Roy Hill      1972
Orange mécanique                    Stanley Kubrick      1971
Le mystère androméde                Robert Wise          1971
La vie privée de sherlock holmes    Billy Wilder         1970
Bucth Cassidy et le kid             George Roy Hill      1969
2001 l'odyssée de l'espace          Stanley Kubrick      1968
La party                            Blake Edwards        1968

La segmentation en 5 pages se fait dans l'appel de la fonction NTILE et la sélection de la bonne page dans la clause WHERE de la requête externe.

Exemple 5 (voir l'annexe E pour les jeux d'essais) - agrégat d'agrégat, recherche des n premiers (top n)

Voici maintenant comment sélectionner les trois metteurs en scène les plus prolifiques,

- en durée :

SELECT FLM_REALISATEUR, DUREE_TOTALE
FROM   (SELECT FLM_REALISATEUR, SUM(FLM_DUREE_MN) AS DUREE_TOTALE,
RANK() OVER(ORDER BY SUM(FLM_DUREE_MN) DESC) AS RANG_DT
FROM   T_FILM_FLM
GROUP  BY FLM_REALISATEUR) AS T
WHERE RANG_DT <= 3

FLM_REALISATEUR                  DUREE_TOTALE
-------------------------------- ------------
Stanley Kubrick                  918
Robert Wise                      875
George Roy Hill                  586

- comme en nombre de films :

SELECT FLM_REALISATEUR, NOMBRE_FILM
FROM   (SELECT FLM_REALISATEUR, SUM(FLM_DUREE_MN) AS DUREE_TOTALE,
RANK() OVER(ORDER BY COUNT(*) DESC) AS NOMBRE_FILM
FROM   T_FILM_FLM
GROUP  BY FLM_REALISATEUR) AS T
WHERE NOMBRE_FILM <= 3

FLM_REALISATEUR                  NOMBRE_FILM
-------------------------------- --------------------
Robert Wise                      1
Stanley Kubrick                  1
Billy Wilder                     3
George Roy Hill                  3

Remarquez que pour cette dernière requête, bien que nous attendions un nombre de lignes égal à 3 dans la réponse, la machine nous en renvoie 4, car elle prend en compte les ex aequo. Le résultat est même pire avec la fonction DENSE_RANK :

SELECT FLM_REALISATEUR, NOMBRE_FILM
FROM   (SELECT FLM_REALISATEUR, SUM(FLM_DUREE_MN) AS DUREE_TOTALE,
DENSE_RANK() OVER(ORDER BY COUNT(*) DESC) AS NOMBRE_FILM
FROM   T_FILM_FLM
GROUP  BY FLM_REALISATEUR) AS T
WHERE NOMBRE_FILM <= 3

FLM_REALISATEUR                  NOMBRE_FILM
-------------------------------- --------------------
Robert Wise                      1
Stanley Kubrick                  1
Billy Wilder                     2
George Roy Hill                  2
Joseph Mankiewicz                3
Orson Welles                     3
Blake Edwards                    3
Alfred Hitchcock                 3

La tentation est alors grande de se servir de la fonction ROW_NUMBER qui numérote les lignes et nous donne par conséquent l'assurance de ne retourner jamais que trois lignes :

SELECT FLM_REALISATEUR, NOMBRE_FILM
FROM   (SELECT FLM_REALISATEUR, SUM(FLM_DUREE_MN) AS DUREE_TOTALE,
ROW_NUMBER() OVER(ORDER BY COUNT(*) DESC) AS NOMBRE_FILM
FROM   T_FILM_FLM
GROUP  BY FLM_REALISATEUR) AS T
WHERE NOMBRE_FILM <= 3

Mais c'est là que la chose devient anti-relationnelle, car non déterministe. En effet, dans ce cas deux solutions sont possibles :

FLM_REALISATEUR                  NOMBRE_FILM
-------------------------------- --------------------
Robert Wise                      1
Stanley Kubrick                  1
Billy Wilder                     3

FLM_REALISATEUR                  NOMBRE_FILM
-------------------------------- --------------------
Robert Wise                      1
Stanley Kubrick                  1
George Roy Hill                  3

Et il est probable que le serveur vous renvoie tantôt l'une, tantôt l'autre. Une catastrophe serait de baser ensuite un traitement sur le résultat d'une telle requête !

Exemple 6 (voir l'annexe E pour les jeux d'essais) - trouver le CA jour le plus important et sa date

SELECT SNC_DATE, CA_JOUR
FROM   (SELECT SNC_DATE, SUM(SNC_BILLET * SNC_TARIF) AS CA_JOUR,
RANK() OVER(ORDER BY SUM(SNC_BILLET * SNC_TARIF) DESC)
AS CLASSEMENT
FROM   dbo.T_SEANCE_SNC
GROUP  BY SNC_DATE) AS T
WHERE  CLASSEMENT = 1

SNC_DATE   CA_JOUR
---------- ------------
2009-01-25 12220.00

Cette requête peut encore s'écrire à l'aide d'une CTE comme suit :

WITH
T AS (SELECT SNC_DATE, SUM(SNC_BILLET * SNC_TARIF) AS CA_JOUR,
RANK() OVER(ORDER BY SUM(SNC_BILLET * SNC_TARIF) DESC)
AS CLASSEMENT
FROM   dbo.T_SEANCE_SNC
GROUP  BY SNC_DATE)
SELECT SNC_DATE, CA_JOUR
FROM   T
WHERE  CLASSEMENT = 1

Sans de fonction fenêtrée cette requête s'écrit :

SELECT SNC_DATE, SUM(SNC_BILLET * SNC_TARIF) AS CA_JOUR
FROM   dbo.T_SEANCE_SNC
GROUP  BY SNC_DATE
HAVING SUM(SNC_BILLET * SNC_TARIF) =
(SELECT MAX(CA_JOUR)
FROM   (SELECT SNC_DATE, SUM(SNC_BILLET * SNC_TARIF) AS CA_JOUR
FROM   dbo.T_SEANCE_SNC
GROUP  BY SNC_DATE) AS T)

NOTA : jusqu'ici nous avons vu la syntaxe simplifiée des fonctions fenêtrées. Voyons maintenant une syntaxe étendue avec l'ajout de la clause de fenêtre proprement dite, que nous allons maintenant détailler…

III-C - Syntaxe avancée d'un appel à une fonction fenêtrée

Cette syntaxe étendue comporte la clause de fenêtrage proprement dite, introduite par le mot clef ROWS ou RANGE. Cette fenêtre permet de ne pas prendre en compte la totalité des données de la requête pour les fonctions fenêtrées, mais de naviguer dans différents sous-groupes, y compris recouvrant :

<fonction_fenêtrage> OVER <fenêtre_de_données>

<fenêtre_de_données> ::=
OVER ( [ PARTITION BY ( <liste_expression_groupage> ) ]
[ ORDER BY <liste_expression_ordonancement> ]
[ { ROWS| RANGE } <cadre_fenêtre> )

<cadre_fenêtre> ::=
{ BETWEEN <début_cadre> AND <fin_cadre>
| UNBOUNDED PRECEDING
| CURRENT ROW
| n PRECEDING }
[ <cadre_exclusion> ]

<début_cadre> ::=
| UNBOUNDED PRECEDING
| CURRENT ROW
| n { PRECEDING | FOLLOWING }
[ <cadre_exclusion> ]

<fin_cadre> ::=
| UNBOUNDED FOLLOWING
| CURRENT ROW
| n { PRECEDING | FOLLOWING }

<cadre_exclusion> ::=
EXCLUDE CURRENT ROW
| EXCLUDE GROUP
| EXCLUDE TIES
| EXCLUDE NO OTHERS

où n est un entier positif.

ROWS et RANGE permettent d'exprimer la taille de la fenêtre. Cette taille peut correspondre à une ou plusieurs lignes d'une partition, ou à toutes les lignes. Vous pouvez l'exprimer en intervalle de valeurs de données par rapport à la valeur de la ligne courante (RANGE), ou en nombre de lignes par rapport à la ligne courante (ROWS).

nPRECEDING : inclusion des n lignes précédentes.

UNBOUNDED PRECEDING : la première ligne de la fenêtre est la première de la partition.

BETWEEN : permet de définir la première et la dernière ligne de la fenêtre, la ligne courante servant comme élément de référence. Les première et dernière lignes sont exprimées en nombre de lignes précédant et suivant respectivement cette ligne courante.

n FOLLOWING : inclusion des n lignes suivantes.

CURRENT ROW : appelle la ligne courante.

UNBOUNDED FOLLOWING : la dernière ligne de la fenêtre est la dernière de la partition.

EXCLUDE CURRENT ROW : exclut la ligne courante de la fenêtre.

EXCLUDE GROUP : exclut la ligne courante et toutes les lignes identiques à la ligne courante de la fenêtre.

EXCLUDE TIES : exclut toutes les lignes à l'exception de la ligne courante et toutes les lignes identiques à la ligne courante de la fenêtre.

NOTA

- En cas d'omission de la clause PARTITION BY, c'est l'intégralité du jeu de résultats qui est pris en compte.

- Si vous utilisez la clause RANGE, vous devez également inclure une clause ORDER BY. En effet, le calcul permettant de générer la fenêtre exige le tri des valeurs. Par ailleurs, la clause ORDER BY ne peut contenir qu'une expression, celle-ci devant renvoyer une date ou une valeur numérique.

- Lorsque vous avez recours aux clauses ROWS ou RANGE, si vous n'indiquez qu'une ligne de départ, la ligne courante correspond à la dernière ligne de la fenêtre. Si vous ne spécifiez qu'une ligne de fin, la ligne courante sert de ligne de départ.

- Si aucune clause ROW ou RANGE n'est spécifiée, la fenêtre est déterminée comme suit :

- elle commence à la première ligne de la partition (UNBOUNDED PRECEDING) ;

- elle se termine par la ligne courante (CURRENT ROW) si une clause ORDER BY est spécifiée, sinon par la dernière (UNBOUNDED FOLLOWING).

Voici une représentation sous la forme d'un diagramme de Backus Naur de la syntaxe de cette fenêtre :

figure 6 : diagramme de Backus Naur des mots clefs du fenêtrage

À ce diagramme, vous pouvez rajouter les exclusions, juste avant le AND et à la fin de l'expression de fenêtre.

Exemple 7 (voir l'annexe E pour les jeux d'essais) - somme cumulative avec niveau de rupture

Nous voudrions savoir quel est le nombre de places vendues par cumul au fil de la journée. De la même manière, nous voulons le chiffre d'affaires correspondant. Dans ce cas, il faut faire la somme des places en reprenant pour chaque jour, les places des séances précédentes…

Voici comment on écrit cela :

SELECT SNC_DATE, SNC_HEURE, FLM_TITRE, SNC_BILLET, SNC_TARIF,
SUM(SNC_BILLET)
OVER(PARTITION BY SNC_DATE ORDER BY SNC_HEURE
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
AS CUMUL_JOUR,
SUM(SNC_BILLET * SNC_TARIF)
OVER(PARTITION BY SNC_DATE ORDER BY SNC_HEURE
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
AS CA_CUMUL_JOUR
FROM   T_SEANCE_SNC AS S
INNER JOIN T_FILM_FLM AS F
ON S.FLM_ID = F.FLM_ID
ORDER BY SNC_DATE, SNC_HEURE

SNC_DATE   SNC_HEURE   FLM_TITRE         SNC_BILLET SNC_TARIF   CUMUL_JOUR  CA_CUMUL_JOUR
---------- ----------- ----------------- ---------- ----------- ----------- ---------------
2009-01-01 14:40:00    West Side Story   94         13.00       94           1222.00
2009-01-01 17:20:00    West Side Story   70         13.00       164          2132.00
2009-01-01 20:00:00    West Side Story   184        13.00       348          4524.00
2009-01-01 22:40:00    West Side Story   148        13.00       496          6448.00
2009-01-02 14:40:00    West Side Story   56         13.00       56            728.00
2009-01-02 17:20:00    West Side Story   216        13.00       272          3536.00
2009-01-02 20:00:00    West Side Story   133        13.00       405          5265.00
2009-01-02 22:40:00    West Side Story   48         13.00       453          5889.00
2009-01-03 14:40:00    West Side Story   104        13.00       104          1352.00
2009-01-03 17:20:00    West Side Story   168        13.00       272          3536.00
2009-01-03 20:00:00    West Side Story   250        13.00       522          6786.00
2009-01-03 22:40:00    West Side Story   73         13.00       595          7735.00
2009-01-04 14:40:00    West Side Story   233        13.00       233          3029.00
2009-01-04 17:20:00    West Side Story   152        13.00       385          5005.00
2009-01-04 20:00:00    West Side Story   201        13.00       586          7618.00
2009-01-04 22:40:00    West Side Story   250        13.00       836         10868.00
2009-01-05 15:12:00    Star Trek         218        12.00       218          2616.00
2009-01-05 17:36:00    Star Trek         93         12.00       311          3732.00
2009-01-05 20:00:00    Star Trek         110        12.00       421          5052.00
2009-01-05 22:24:00    Star Trek         34         12.00       455          5460.00
...

On peut symboliser graphiquement cet « emboîtage » de fenêtres comme ceci :

figure 7 : fenêtres emboîtes pour cumuls successifs

Bien entendu, il est possible de réaliser une telle requête sans ces opérateurs et utilisant des sous-requêtes dans la clause SELECT :

SELECT SNC_DATE, SNC_HEURE, FLM_TITRE, SNC_BILLET, SNC_TARIF,
(SELECT SUM(SNC_BILLET)
FROM   T_SEANCE_SNC AS Si
WHERE  Si.SNC_DATE = S.SNC_DATE
AND  Si.SNC_HEURE <= S.SNC_HEURE)
AS CUMUL_JOUR,
(SELECT SUM(SNC_BILLET * SNC_TARIF)
FROM   T_SEANCE_SNC AS Si
WHERE  Si.SNC_DATE = S.SNC_DATE
AND  Si.SNC_HEURE <= S.SNC_HEURE) AS CA_CUMUL_JOUR
FROM   T_SEANCE_SNC AS S
INNER JOIN T_FILM_FLM AS F
ON S.FLM_ID = F.FLM_ID
ORDER BY SNC_DATE, SNC_HEURE

À nouveau, si l'on regarde les plans de requêtes générés ainsi que leur cout, l'avantage revient aux fonctions fenêtrées :

Comparaison des plans de requête sous PostGreSQL

figure 8 : plan de requête PostGreSQL

Avec les fonctions fenêtrées, ce plan coute 179.73

figure 9 : plan de requête PostGreSQL

Sans les fonctions fenêtrées, ce plan coute 92 593.73

Une autre vision de la chose peut être apportée comme suit :

figure 10 : représentation de la notion de cumuls avec rupture

Nous n'avons fait figurer que le cumul du nombre de billets vendus par séance dans la journée pour simplifier notre démonstration.

Exemple 8 (voir l'annexe E pour les jeux d'essais) - cumul avec fenêtre glissante

Voyons maintenant comment on peut obtenir le chiffre d'affaires de notre cinéma par semaine glissante, c'est-à-dire en cumulant sur les 6 jours précédents avec le jour courant :

SELECT SNC_DATE, SNC_HEURE, FLM_TITRE, SNC_BILLET, SNC_TARIF,
SUM(SNC_BILLET * SNC_TARIF)
OVER (ORDER BY SNC_DATE
RANGE BETWEEN 6 PRECEDING
AND CURRENT ROW) AS CA_CUMUL_GLISSANT_SEMAINE
FROM   T_SEANCE_SNC AS S
INNER JOIN T_FILM_FLM AS F
ON S.FLM_ID = F.FLM_ID
ORDER BY SNC_DATE, SNC_HEURE

CA_CUMUL
_GLISSANT
SNC_DATE   SNC_HEURE    FLM_TITRE                    SNC_BILLET SNC_TARIF    _SEMAINE
---------- ------------ ---------------------------- ---------- ------------ ----------
2009-01-01 14:40:00     West Side Story              94         13.00         6448.00
2009-01-01 17:20:00     West Side Story              70         13.00         6448.00
2009-01-01 20:00:00     West Side Story              184        13.00         6448.00
2009-01-01 22:40:00     West Side Story              148        13.00         6448.00
2009-01-02 14:40:00     West Side Story              56         13.00        12337.00
2009-01-02 17:20:00     West Side Story              216        13.00        12337.00
2009-01-02 20:00:00     West Side Story              133        13.00        12337.00
2009-01-02 22:40:00     West Side Story              48         13.00        12337.00
2009-01-03 14:40:00     West Side Story              104        13.00        20072.00
2009-01-03 17:20:00     West Side Story              168        13.00        20072.00
2009-01-03 20:00:00     West Side Story              250        13.00        20072.00
2009-01-03 22:40:00     West Side Story              73         13.00        20072.00
2009-01-04 14:40:00     West Side Story              233        13.00        30940.00
2009-01-04 17:20:00     West Side Story              152        13.00        30940.00
2009-01-04 20:00:00     West Side Story              201        13.00        30940.00
2009-01-04 22:40:00     West Side Story              250        13.00        30940.00
2009-01-05 15:12:00     Star Trek                    218        12.00        36400.00
2009-01-05 17:36:00     Star Trek                    93         12.00        36400.00
2009-01-05 20:00:00     Star Trek                    110        12.00        36400.00
2009-01-05 22:24:00     Star Trek                    34         12.00        36400.00
2009-01-06 15:12:00     Star Trek                    129        12.00        42664.00
2009-01-06 17:36:00     Star Trek                    188        12.00        42664.00
2009-01-06 20:00:00     Star Trek                    192        12.00        42664.00
2009-01-06 22:24:00     Star Trek                    13         12.00        42664.00
2009-01-07 15:12:00     Star Trek                    245        12.00        48844.00
2009-01-07 17:36:00     Star Trek                    70         12.00        48844.00
2009-01-07 20:00:00     Star Trek                    12         12.00        48844.00
2009-01-07 22:24:00     Star Trek                    188        12.00        48844.00
2009-01-08 15:12:00     Star Trek                    138        12.00        50112.00
2009-01-08 17:36:00     Star Trek                    244        12.00        50112.00
2009-01-08 20:00:00     Star Trek                    62         12.00        50112.00
2009-01-08 22:24:00     Star Trek                    199        12.00        50112.00
2009-01-09 15:12:00     Star Trek                    7          12.00        50259.00
2009-01-09 17:36:00     Star Trek                    250        12.00        50259.00
2009-01-09 20:00:00     Star Trek                    184        12.00        50259.00
2009-01-09 22:24:00     Star Trek                    62         12.00        50259.00
2009-01-10 15:12:00     Star Trek                    222        12.00        52724.00
2009-01-10 17:36:00     Star Trek                    192        12.00        52724.00
2009-01-10 20:00:00     Star Trek                    205        12.00        52724.00
2009-01-10 22:24:00     Star Trek                    231        12.00        52724.00
2009-01-11 15:12:00     Star Trek                    184        12.00        50880.00
2009-01-11 17:36:00     Star Trek                    114        12.00        50880.00
2009-01-11 20:00:00     Star Trek                    239        12.00        50880.00
2009-01-11 22:24:00     Star Trek                    215        12.00        50880.00
2009-01-12 14:48:00     2001 l'odyssée de l'espace   113        13.00        52830.00
2009-01-12 17:24:00     2001 l'odyssée de l'espace   152        13.00        52830.00
2009-01-12 20:00:00     2001 l'odyssée de l'espace   135        13.00        52830.00
2009-01-12 22:36:00     2001 l'odyssée de l'espace   170        13.00        52830.00
2009-01-13 14:48:00     2001 l'odyssée de l'espace   108        13.00        54249.00
2009-01-13 17:24:00     2001 l'odyssée de l'espace   189        13.00        54249.00
2009-01-13 20:00:00     2001 l'odyssée de l'espace   187        13.00        54249.00
2009-01-13 22:36:00     2001 l'odyssée de l'espace   107        13.00        54249.00
2009-01-14 14:48:00     2001 l'odyssée de l'espace   191        13.00        55856.00
2009-01-14 17:24:00     2001 l'odyssée de l'espace   147        13.00        55856.00
2009-01-14 20:00:00     2001 l'odyssée de l'espace   100        13.00        55856.00
2009-01-14 22:36:00     2001 l'odyssée de l'espace   161        13.00        55856.00
2009-01-15 14:48:00     2001 l'odyssée de l'espace   168        13.00        55095.00
2009-01-15 17:24:00     2001 l'odyssée de l'espace   7          13.00        55095.00
2009-01-15 20:00:00     2001 l'odyssée de l'espace   138        13.00        55095.00
2009-01-15 22:36:00     2001 l'odyssée de l'espace   222        13.00        55095.00
...

On peut visualiser ce concept de fenêtre glissante par l'image suivante :

figure 11 : fenêtre glissante

Sans les fonctions fenêtrées, cette requête s'écrit :

SELECT SNC_DATE, SNC_HEURE, FLM_TITRE, SNC_BILLET, SNC_TARIF,
(SELECT SUM(SNC_BILLET * SNC_TARIF)
FROM   T_SEANCE_SNC AS Si
WHERE  Si.SNC_DATE BETWEEN DATEADD(DAY, -6, S.SNC_DATE)
AND S.SNC_DATE) AS CA_CUMUL_GLISSANT_SEMAINE
FROM   T_SEANCE_SNC AS S
INNER JOIN T_FILM_FLM AS F
ON S.FLM_ID = F.FLM_ID
ORDER BY SNC_DATE, SNC_HEURE

Sous Oracle, la différence dans les plans de requête est aussi spectaculaire :

Avec les fonctions fenêtrées :

figure 12 : plan de requête Oracle

Sans les fonctions fenêtrées :

figure 13 : plan de requête Oracle

Les éléments en rouge montrent qu'il manque un index pour que la seconde requête soit efficace, alors que la première n'en a pas besoin…

Exemple 9 (voir l'annexe E pour les jeux d'essais) - cumul avec fenêtre glissante de 7 jours avant, 7 jours centrés et 7 jours après la ligne courante

SELECT SNC_DATE, SNC_HEURE, SNC_BILLET, SNC_TARIF,
SUM(SNC_BILLET * SNC_TARIF)
OVER (ORDER BY SNC_DATE
RANGE BETWEEN 7 PRECEDING
AND CURRENT ROW EXCLUDE CURRENT ROW)
AS CA_CUMUL_GLISSANT_SEMAINE_AVANT_JOUR,
SUM(SNC_BILLET * SNC_TARIF)
OVER (ORDER BY SNC_DATE
RANGE BETWEEN 3 PRECEDING
AND 3 FOLLOWING)
AS CA_CUMUL_GLISSANT_SEMAINE_PENDANT_JOUR,
SUM(SNC_BILLET * SNC_TARIF)
OVER (ORDER BY SNC_DATE
RANGE BETWEEN CURRENT ROW EXCLUDE CURRENT ROW
AND 7 FOLLOWING)
AS CA_CUMUL_GLISSANT_SEMAINE_APRES_JOUR
FROM   T_SEANCE_SNC AS S
INNER JOIN T_FILM_FLM AS F
ON S.FLM_ID = F.FLM_ID
ORDER BY SNC_DATE, SNC_HEURE

Ou encore :

SELECT SNC_DATE, SNC_HEURE, SNC_BILLET, SNC_TARIF,
SUM(SNC_BILLET * SNC_TARIF)
OVER (ORDER BY SNC_DATE
RANGE BETWEEN 7 PRECEDING
AND 1 PRECEDING)
AS CA_CUMUL_GLISSANT_SEMAINE_AVANT_JOUR,
SUM(SNC_BILLET * SNC_TARIF)
OVER (ORDER BY SNC_DATE
RANGE BETWEEN 3 PRECEDING
AND 3 FOLLOWING)
AS CA_CUMUL_GLISSANT_SEMAINE_PENDANT_JOUR,
SUM(SNC_BILLET * SNC_TARIF)
OVER (ORDER BY SNC_DATE
RANGE BETWEEN 1 FOLLOWING
AND 7 FOLLOWING)
AS CA_CUMUL_GLISSANT_SEMAINE_APRES_JOUR
FROM   T_SEANCE_SNC AS S
INNER JOIN T_FILM_FLM AS F
ON S.FLM_ID = F.FLM_ID
ORDER BY SNC_DATE, SNC_HEURE

CA_CUMUL_GLISSANT_SEMAINE_
SNC_DATE   SNC_HEURE   SNC_BILLET SNC_TARIF   AVANT_JOUR   PENDANT_JOUR   APRES_JO
---------- ----------- ---------- ----------- ------------ -------------- --------
2009-01-01 14:40:00    94         13.00           NULL     30940.00       48844.00
2009-01-01 17:20:00    70         13.00           NULL     30940.00       48844.00
2009-01-01 20:00:00    184        13.00           NULL     30940.00       48844.00
2009-01-01 22:40:00    148        13.00           NULL     30940.00       48844.00
2009-01-02 14:40:00    56         13.00        6448.00     36400.00       50112.00
2009-01-02 17:20:00    216        13.00        6448.00     36400.00       50112.00
2009-01-02 20:00:00    133        13.00        6448.00     36400.00       50112.00
2009-01-02 22:40:00    48         13.00        6448.00     36400.00       50112.00
2009-01-03 14:40:00    104        13.00       12337.00     42664.00       50259.00
2009-01-03 17:20:00    168        13.00       12337.00     42664.00       50259.00
2009-01-03 20:00:00    250        13.00       12337.00     42664.00       50259.00
2009-01-03 22:40:00    73         13.00       12337.00     42664.00       50259.00
2009-01-04 14:40:00    233        13.00       20072.00     48844.00       52724.00
2009-01-04 17:20:00    152        13.00       20072.00     48844.00       52724.00
2009-01-04 20:00:00    201        13.00       20072.00     48844.00       52724.00
2009-01-04 22:40:00    250        13.00       20072.00     48844.00       52724.00
2009-01-05 15:12:00    218        12.00       30940.00     50112.00       50880.00
2009-01-05 17:36:00    93         12.00       30940.00     50112.00       50880.00
2009-01-05 20:00:00    110        12.00       30940.00     50112.00       50880.00
2009-01-05 22:24:00    34         12.00       30940.00     50112.00       50880.00
2009-01-06 15:12:00    129        12.00       36400.00     50259.00       52830.00
2009-01-06 17:36:00    188        12.00       36400.00     50259.00       52830.00
2009-01-06 20:00:00    192        12.00       36400.00     50259.00       52830.00
2009-01-06 22:24:00    13         12.00       36400.00     50259.00       52830.00
2009-01-07 15:12:00    245        12.00       42664.00     52724.00       54249.00
2009-01-07 17:36:00    70         12.00       42664.00     52724.00       54249.00
2009-01-07 20:00:00    12         12.00       42664.00     52724.00       54249.00
2009-01-07 22:24:00    188        12.00       42664.00     52724.00       54249.00
2009-01-08 15:12:00    138        12.00       48844.00     50880.00       55856.00
2009-01-08 17:36:00    244        12.00       48844.00     50880.00       55856.00
2009-01-08 20:00:00    62         12.00       48844.00     50880.00       55856.00
2009-01-08 22:24:00    199        12.00       48844.00     50880.00       55856.00
2009-01-09 15:12:00    7          12.00       50112.00     52830.00       55095.00
2009-01-09 17:36:00    250        12.00       50112.00     52830.00       55095.00
2009-01-09 20:00:00    184        12.00       50112.00     52830.00       55095.00
2009-01-09 22:24:00    62         12.00       50112.00     52830.00       55095.00
2009-01-10 15:12:00    222        12.00       50259.00     54249.00       59277.00
2009-01-10 17:36:00    192        12.00       50259.00     54249.00       59277.00
2009-01-10 20:00:00    205        12.00       50259.00     54249.00       59277.00
2009-01-10 22:24:00    231        12.00       50259.00     54249.00       59277.00
2009-01-11 15:12:00    184        12.00       52724.00     55856.00       55109.00
2009-01-11 17:36:00    114        12.00       52724.00     55856.00       55109.00
2009-01-11 20:00:00    239        12.00       52724.00     55856.00       55109.00
2009-01-11 22:24:00    215        12.00       52724.00     55856.00       55109.00
2009-01-12 14:48:00    113        13.00       50880.00     55095.00       54873.00
2009-01-12 17:24:00    152        13.00       50880.00     55095.00       54873.00
2009-01-12 20:00:00    135        13.00       50880.00     55095.00       54873.00
2009-01-12 22:36:00    170        13.00       50880.00     55095.00       54873.00
2009-01-13 14:48:00    108        13.00       52830.00     59277.00       50830.00
2009-01-13 17:24:00    189        13.00       52830.00     59277.00       50830.00
2009-01-13 20:00:00    187        13.00       52830.00     59277.00       50830.00
2009-01-13 22:36:00    107        13.00       52830.00     59277.00       50830.00
2009-01-14 14:48:00    191        13.00       54249.00     55109.00       47242.00
2009-01-14 17:24:00    147        13.00       54249.00     55109.00       47242.00
2009-01-14 20:00:00    100        13.00       54249.00     55109.00       47242.00
2009-01-14 22:36:00    161        13.00       54249.00     55109.00       47242.00
2009-01-15 14:48:00    168        13.00       55856.00     54873.00       45851.00
2009-01-15 17:24:00    7          13.00       55856.00     54873.00       45851.00
2009-01-15 20:00:00    138        13.00       55856.00     54873.00       45851.00
2009-01-15 22:36:00    222        13.00       55856.00     54873.00       45851.00
...

À nouveau cette requête est exprimable sans les fonctions fenêtrées :

SELECT SNC_DATE, SNC_HEURE, SNC_BILLET, SNC_TARIF,
(SELECT SUM(SNC_BILLET * SNC_TARIF)
FROM   T_SEANCE_SNC AS Si
WHERE  Si.SNC_DATE >= DATEADD(DAY, -7, S.SNC_DATE)
AND  Si.SNC_DATE <  S.SNC_DATE)
AS CA_CUMUL_GLISSANT_SEMAINE_AVANT_JOUR,
(SELECT SUM(SNC_BILLET * SNC_TARIF)
FROM   T_SEANCE_SNC AS Si
WHERE  Si.SNC_DATE BETWEEN DATEADD(DAY, -3, S.SNC_DATE)
AND DATEADD(DAY, +3, S.SNC_DATE) )
AS CA_CUMUL_GLISSANT_SEMAINE_PENDANT_JOUR,
(SELECT SUM(SNC_BILLET * SNC_TARIF)
FROM   T_SEANCE_SNC AS Si
WHERE  Si.SNC_DATE > S.SNC_DATE
AND  Si.SNC_DATE <=  DATEADD(DAY,  7, S.SNC_DATE) )
AS CA_CUMUL_GLISSANT_SEMAINE_APRES_JOUR
FROM   T_SEANCE_SNC AS S
INNER JOIN T_FILM_FLM AS F
ON S.FLM_ID = F.FLM_ID
ORDER BY SNC_DATE, SNC_HEURE

Exemple 10 (voir l'annexe E pour les jeux d'essais) - cumuls autour d'une valeur

On cherche maintenant à afficher le CA du jour courant avec celui du jour d'avant et du jour d'après. On retiendra aussi le nombre de billets vendus dans le jour et le tarif moyen au cours du jour. On ne tient pas compte des horaires :

SELECT DISTINCT SNC_DATE,
SUM(SNC_BILLET) OVER(PARTITION BY SNC_DATE) AS TOTAL_BILLET,
AVG(SNC_TARIF)  OVER(PARTITION BY SNC_DATE) AS TARIF_MOYEN,
SUM(SNC_BILLET * SNC_TARIF)
OVER(ORDER BY SNC_DATE
RANGE BETWEEN 1 PRECEDING
AND 1 PRECEDING) AS CA_JOUR_AVANT,
SUM(SNC_BILLET * SNC_TARIF)
OVER(PARTITION BY SNC_DATE) AS CA_JOUR,
SUM(SNC_BILLET * SNC_TARIF)
OVER(ORDER BY SNC_DATE
RANGE BETWEEN 1 FOLLOWING
AND 1 FOLLOWING) AS CA_JOUR_APRES
FROM   T_SEANCE_SNC AS S
INNER JOIN T_FILM_FLM AS F
ON S.FLM_ID = F.FLM_ID
ORDER BY SNC_DATE

SNC_DATE   TOTAL_BILLET TARIF_MOYEN    CA_JOUR_AVANT  CA_JOUR    CA_JOUR_APRES
---------- ------------ -------------- -------------- ---------- ---------------
2009-01-01 496          13.000000      NULL           6448.00    5889.00
2009-01-02 453          13.000000      6448.00        5889.00    7735.00
2009-01-03 595          13.000000      5889.00        7735.00    10868.00
2009-01-04 836          13.000000      7735.00        10868.00   5460.00
2009-01-05 455          12.000000      10868.00       5460.00    6264.00
2009-01-06 522          12.000000      5460.00        6264.00    6180.00
2009-01-07 515          12.000000      6264.00        6180.00    7716.00
2009-01-08 643          12.000000      6180.00        7716.00    6036.00
2009-01-09 503          12.000000      7716.00        6036.00    10200.00
2009-01-10 850          12.000000      6036.00        10200.00   9024.00
2009-01-11 752          12.000000      10200.00       9024.00    7410.00
2009-01-12 570          13.000000      9024.00        7410.00    7683.00
2009-01-13 591          13.000000      7410.00        7683.00    7787.00
2009-01-14 599          13.000000      7683.00        7787.00    6955.00
2009-01-15 535          13.000000      7787.00        6955.00    10218.00
2009-01-16 786          13.000000      6955.00        10218.00   6032.00
2009-01-17 464          13.000000      10218.00       6032.00    8788.00
2009-01-18 676          13.000000      6032.00        8788.00    3367.00
2009-01-19 259          13.000000      8788.00        3367.00    4095.00
2009-01-20 315          13.000000      3367.00        4095.00    6396.00
2009-01-21 492          13.000000      4095.00        6396.00    5161.00
2009-01-22 397          13.000000      6396.00        5161.00    9607.00
2009-01-23 739          13.000000      5161.00        9607.00    5343.00
2009-01-24 411          13.000000      9607.00        5343.00    12220.00
2009-01-25 940          13.000000      5343.00        12220.00   7920.00
2009-01-26 720          11.000000      12220.00       7920.00    5885.00
2009-01-27 535          11.000000      7920.00        5885.00    6446.00
2009-01-28 586          11.000000      5885.00        6446.00    4873.00
2009-01-29 443          11.000000      6446.00        4873.00    3168.00
2009-01-30 288          11.000000      4873.00        3168.00    6501.00
2009-01-31 591          11.000000      3168.00        6501.00    5709.00
...

Une autre façon d'écrire cette requête est d'utiliser une imbrication des fonctions fenêtrées, par exemple dans une CTE et cela grâce aux fonctions LEAD et LAG qui permettent d'avancer ou de reculer dans les lignes :

WITH
T AS (SELECT DISTINCT SNC_DATE,
SUM(SNC_BILLET) OVER(PARTITION BY SNC_DATE) AS TOTAL_BILLET,
AVG(SNC_TARIF)  OVER(PARTITION BY SNC_DATE) AS TARIF_MOYEN,
SUM(SNC_BILLET * SNC_TARIF)
OVER(PARTITION BY SNC_DATE) AS CA_JOUR
FROM   T_SEANCE_SNC AS S
INNER JOIN T_FILM_FLM AS F
ON S.FLM_ID = F.FLM_ID)
SELECT SNC_DATE, TOTAL_BILLET, TARIF_MOYEN,
LAG(CA_JOUR, 1) OVER(ORDER BY SNC_DATE) AS CA_JOUR_AVANT,
CA_JOUR,
LEAD(CA_JOUR, 1) OVER(ORDER BY SNC_DATE) AS CA_JOUR_AVANT
FROM   T
ORDER  BY SNC_DATE

À nouveau une telle requête peut s'exprimer sans recourir aux fonctions fenêtrées :

SELECT DISTINCT SNC_DATE,
SUM(SNC_BILLET) AS TOTAL_BILLET,
AVG(SNC_TARIF) AS TARIF_MOYEN,
(SELECT SUM(SNC_BILLET * SNC_TARIF)
FROM   T_SEANCE_SNC AS Si
WHERE  Si.SNC_DATE = DATEADD(DAY, -1, S.SNC_DATE)) AS CA_JOUR_AVANT,
(SELECT SUM(SNC_BILLET * SNC_TARIF)
FROM   T_SEANCE_SNC AS Si
WHERE  Si.SNC_DATE = S.SNC_DATE) AS CA_JOUR,
(SELECT SUM(SNC_BILLET * SNC_TARIF)
FROM   T_SEANCE_SNC AS Si
WHERE  Si.SNC_DATE = DATEADD(DAY, 1, S.SNC_DATE)) AS CA_JOUR_APRES
FROM   T_SEANCE_SNC AS S
INNER JOIN T_FILM_FLM AS F
ON S.FLM_ID = F.FLM_ID
GROUP BY SNC_DATE
ORDER BY SNC_DATE

Exemple 11 (voir l'annexe E pour les jeux d'essais) - calcul d'une médiane

Nous allons maintenant afficher le CA séance, le CA du jour, de la semaine et du mois, ainsi que le CA moyen et le CA médian pour les jours, semaines et mois. Rappelons que le calcul de la médiane n'est pas disponible dans SQL, mais qu'il s'agit de la valeur du « milieu » (ou la moyenne des valeurs du milieu si le nombre d'échantillons est pair) lorsque l'on ordonne toutes les valeurs.

WITH
T1 AS
(
SELECT SNC_DATE, SNC_HEURE, SNC_BILLET, SNC_TARIF, SNC_BILLET * SNC_TARIF AS CA_SEANCE,
SUM(SNC_BILLET * SNC_TARIF)
OVER(PARTITION BY SNC_DATE) AS CA_JOUR,
SUM(SNC_BILLET * SNC_TARIF)
OVER(PARTITION BY YEAR(SNC_DATE), DATEPART(isowk, SNC_DATE))
AS CA_SEMAINE,
SUM(SNC_BILLET * SNC_TARIF)
OVER(PARTITION BY YEAR(SNC_DATE), MONTH(SNC_DATE))
AS CA_MOIS
FROM   T_SEANCE_SNC
),
T2 AS
(
SELECT SNC_DATE, SNC_HEURE, SNC_BILLET, SNC_TARIF, CA_SEANCE, CA_JOUR, CA_SEMAINE, CA_MOIS,
AVG(CA_JOUR) OVER() AS CA_MOYEN_JOUR,
AVG(CA_SEMAINE) OVER() AS CA_MOYEN_SEMAINE,
AVG(CA_MOIS) OVER() AS CA_MOYEN_MOIS,
ROW_NUMBER() OVER(ORDER BY CA_JOUR) AS CA_JOUR_ORDRE,
ROW_NUMBER() OVER(ORDER BY CA_SEMAINE) AS CA_SEMAINE_ORDRE,
ROW_NUMBER() OVER(ORDER BY CA_MOIS) AS CA_MOIS_ORDRE,
COUNT(*) OVER() AS N
FROM   T1
)
SELECT SNC_DATE, SNC_HEURE, SNC_BILLET, SNC_TARIF, CA_SEANCE, CA_JOUR, CA_SEMAINE, CA_MOIS,
CA_MOYEN_JOUR, CA_MOYEN_SEMAINE, CA_MOYEN_MOIS,
(SELECT AVG(CA_JOUR)
FROM   T2
WHERE  CA_JOUR_ORDRE BETWEEN FLOOR((N -0.5) / 2) + 1
AND CEILING((N + 0.5) / 2))
AS CA_MEDIAN_JOUR,
(SELECT AVG(CA_SEMAINE)
FROM   T2
WHERE  CA_SEMAINE_ORDRE BETWEEN FLOOR((N -0.5) / 2) + 1
AND CEILING((N + 0.5) / 2))
AS CA_MEDIAN_SEMAINE,
(SELECT AVG(CA_MOIS)
FROM   T2
WHERE  CA_MOIS_ORDRE BETWEEN FLOOR((N -0.5) / 2) + 1
AND CEILING((N + 0.5) / 2))
AS CA_MEDIAN_MOIS
FROM   T2

NOTA : cette requête est donnée sous MS SQL Server. Sur d'autres SGBDR, il faudra remplacer DATEPART(isowk, SNC_DATE) par WEEK(SNC_DATE) et pour d'autres, utiliser la fonction EXTRACT pour obtenir la partie souhaitable de la date.

Comme nous venons de le voir, cette requête est complexe et son plan de requête l'est aussi. Mais sans les fonctions fenêtrées, la complexité de mise au point de cette requête explose ! Et les temps de calcul deviennent gigantesques…

Nous allons voir maintenant un dernier « truc » qui va nous permettre de simplifier grandement cette requête… Voir paragraphe suivant.

IV - PERCENTILE… et WITHIN GROUP

L'option WITHIN GROUP, utilisable uniquement avec les fonctions PERCENTILE_CONT et PERCENTILE_DISC (classement par 100e), permet d'obtenir rapidement une valeur à un point précis d'un classement. CONT agit sur des valeurs continues (interpolation linéaire) et DISC sur des valeurs discrètes.

La syntaxe de WITHIN GROUP est la suivante :

WITHIN GROUP
(ORDER BY <expression1> [ DESC | ASC ] [ NULLS { FIRST | LAST } ]
[, <expression2> [ DESC | ASC ] [ NULLS { FIRST | LAST } ]
[, ... ] )

Exemple 12 (voir l'annexe E pour les jeux d'essais) - calcul d'une médiane (reprise de l'exemple 11)

WITH
T1 AS
(
SELECT SNC_DATE, SNC_HEURE, SNC_BILLET, SNC_TARIF, SNC_BILLET * SNC_TARIF AS CA_SEANCE,
SUM(SNC_BILLET * SNC_TARIF)
OVER(PARTITION BY SNC_DATE) AS CA_JOUR,
SUM(SNC_BILLET * SNC_TARIF)
OVER(PARTITION BY YEAR(SNC_DATE), WEEK(SNC_DATE))
AS CA_SEMAINE,
SUM(SNC_BILLET * SNC_TARIF)
OVER(PARTITION BY YEAR(SNC_DATE), MONTH(SNC_DATE))
AS CA_MOIS
FROM   dbo.T_SEANCE_SNC
)
SELECT SNC_DATE, SNC_HEURE, SNC_BILLET, SNC_TARIF,
CA_SEANCE, CA_JOUR, CA_SEMAINE, CA_MOIS,
AVG(CA_JOUR) OVER() AS CA_MOYEN_JOUR,
AVG(CA_SEMAINE) OVER() AS CA_MOYEN_SEMAINE,
AVG(CA_MOIS) OVER() AS CA_MOYEN_MOIS,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY CA_JOUR)
AS CA_MEDIAN_JOUR,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY CA_SEMAINE)
AS CA_MEDIAN_SEMAINE,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY CA_MOIS)
AS CA_MEDIAN_MOIS
FROM   T1
GROUP  BY SNC_DATE, SNC_HEURE, SNC_BILLET, SNC_TARIF,
CA_SEANCE, CA_JOUR,  CA_SEMAINE, CA_MOIS

En effet la valeur 0,5 passée en argument de la fonction PERCENTILE_CONT permet de retrouver la valeur médiane !

Attention : les valeurs calculées peuvent différer sensiblement entre la requête pour laquelle on calcule les valeurs médianes par moyenne et la fonction PERCENTILE_CONT, plus juste, qui réalise une interpolation linéaire.

V - Utilisation de la clause WINDOW

Le but de la clause WINDOW qui se place dans le SELECT juste avant la clause de tri (ORDER BY), est de fournir des fenêtres de données prédéfinies aux différentes fonctions qui le nécessitent. Ces fonctions devront donc reprendre le nom de la fenêtre dans la partie PARTITION de l'appel de clause OVER.

Comprenons que la clause OVER est une fenêtre WINDOW particulière, mais que la clause WINDOW permet de globaliser la fenêtre.

Comprenons aussi qu'une fenêtre n'est en fait rien d'autre que la définition d'une table dont les lignes ont un comportement particulier.

Exemple 13 (voir l'annexe E pour les jeux d'essais) - utilisation de la clause WINDOW (reprise de l'exemple 2)

SELECT FLM_TITRE, FLM_REALISATEUR,
MIN(FLM_ANNEE) OVER W_P_REA
AS AN_PLUS_VIEUX_FILM_REAL,
MAX(FLM_ANNEE) OVER W_P_REA
AS AN_PLUS_RECENT_FILM_REAL,
RANK() OVER W_O_FAN
AS CHRONO_FILM,
RANK() OVER W_P_REA_O_FAN
AS CHRONO_FILM_REAL
FROM   T_FILM_FLM
WINDOW W_P_REA AS (PARTITION BY FLM_REALISATEUR),
W_O_FAN AS (ORDER BY FLM_ANNEE),
W_P_REA_O_FAN AS (PARTITION BY FLM_REALISATEUR ORDER BY FLM_ANNEE)
ORDER  BY FLM_REALISATEUR

VI - Position des NULL

Les mots clefs NULL FIRST et NULL LAST peuvent être utilisés dans la partie ORDER BY de la clause OVER afin de préciser si les valeurs absentes doivent être positionnées au début ou en fin de tri.

Exemple 14 (voir l'annexe E pour les jeux d'essais) - utilisation du mot clef NULL FIRST

SELECT FLM_TITRE, FLM_REALISATEUR,
RANK() OVER(PARTITION BY FLM_REALISATEUR
ORDER BY FLM_ANNEE NULL FIRST)
AS CHRONO_FILM_REAL
FROM   T_FILM_FLM
ORDER  BY FLM_REALISATEUR

VII - Notes techniques

La plupart de ces requêtes sont compatibles avec PostGreSQL, MS SQL Server et Oracle. Néanmoins la version d'Oracle Express sur laquelle nous avons testé ces requêtes (10g release 2) n'accepte pas des SELECT avec l'étoile ni de nommer les tables dérivées (AS T). En sus, Oracle effectue des transtypages illicites qui rendent certaines requêtes inopérables et interdit d'utiliser des noms d'objet de plus de 30 caractères.

Par exemple, la requête suivante (exemple 4) :

SELECT FLM_TITRE, FLM_REALISATEUR, FLM_ANNEE
FROM   (SELECT FLM_TITRE, FLM_REALISATEUR, FLM_ANNEE,
ROW_NUMBER() OVER(ORDER BY FLM_ANNEE DESC) - 1 AS N
FROM   T_FILM_FLM) AS T
WHERE  (N / 5) + 1 = 2

…doit être réécrite ainsi pour Oracle :

SELECT FLM_TITRE, FLM_REALISATEUR, FLM_ANNEE
FROM   (SELECT FLM_TITRE, FLM_REALISATEUR, FLM_ANNEE,
ROW_NUMBER() OVER(ORDER BY FLM_ANNEE DESC) - 1 AS N
FROM   T_FILM_FLM) --> suppression de l'alias de table
WHERE  FLOOR((N / 5) + 1) = 2 --> rajout de la fonction FLOOR

Enfin, la norme SQL indique qu'un nom d'objet est limité à 128 caractères, mais Oracle refuse plus d'une trentaine, tant et si bien que l'on doit, par exemple, renommer CA_CUMUL_GLISSANT_SEMAINE_AVANT_JOUR en CA_CGSAJ et autres colonnes dans la requête de l'exemple 9.

La fonction Oracle pour extraire le numéro ISO de la semaine est TO_NUMBER(TO_CHAR(SYSDATE, 'WI')).

Remerciement :

- à Richard Gaillard dit « Pomalaix » et Stéphane Faroult de RoughSea pour leurs conseils sur Oracle.

VIII - ANNEXE A - liste des fonctions d'agrégation

fonction

description

Type

COUNT(*)

compte le nombre de lignes

tous

COUNT(exp)

compte le nombre d'occurrences

tous

MAX(exp)

renvoie le maximum

tous

MIN(exp)

renvoie le minimum

tous

SUM(exp)

sommation

numérique

AVG(exp)

calcule la moyenne

numérique

EVERY(exp)

renvoie un booléen SQL qui vaut vrai si les occurrences sont toutes vraies

booléen

ANY(exp)SOME(exp)

renvoie un booléen SQL qui vaut vrai si au moins une occurrence est vraie

booléen

STDDEV_POP(exp)

(nouveauté SQL:2003) écart type d'une population

numérique

STDDEV_SAMP(exp)

(nouveauté SQL:2003) écart type d'un échantillon

numérique

VAR_POP(exp)

(nouveauté SQL:2003) variance d'une population

numérique

VAR_SAMP(exp)

(nouveauté SQL:2003) variance d'un échantillon

numérique

PERCENTILE_CONT(exp)

valeur interpolée d'après pourcentage

numérique

PERCENTILE_DIST(exp)

plus petite distribution cumulée supérieure ou égale au pourcentage indiqué

numérique

où (exp) est une expression basée sur les colonnes de la table.

IX - ANNEXE B - liste des fonctions de classement

fonction

définition

RANK()

classement absolu avec prise en compte des ex aequo par saut

DENSE_RANK()

classement absolu sans prise en compte des ex aequo par saut

PERCENT_RANK()

classement en pourcentage

CUM_DIST()

position relative de la valeur. Résultat dans l'intervalle ] 0, 1 ]

ROW_NUMBER()

numérotation des lignes

NTILE(n)

définition de n-tile (décile : n=10, centile : n=100…)

Utilisable uniquement avec un fenêtrage explicite :

fonction

définition

LEAD (exp [, offset] [, defaut] )

valeur relative avant dans la fenêtre

LAG (exp [, offset] [, defaut] )

valeur relative après dans la fenêtre

FIRST_VALUE

première valeur dans la fenêtre

LAST_VALUE

dernière valeur dans la fenêtre

NTH_VALUE

énième valeur dans la fenêtre

X - ANNEXE C - position des éditeurs sur les fonctions fenêtrées

SGBDR

IBM DB2

Oracle

MS SQL Server

Sybase AS A

PostGreSQL

FireBird

MySQL

Access

Version

9

11G

2008

9.0.1

8.4

2.1

5.0

2007

Agrégats

Oui

Oui

Oui

Oui

Oui

Non

Non

Non

Analytiques

Oui

Oui

Oui

Oui

Oui

Non

Non

Non

Windows

Non

Non

Non

Oui

Oui

Non

Non

Non

Range/Rows

Oui

Oui

Non

Oui

Oui (1)

Non

Non

Non

Within Group

Oui

Oui

Non

Non

Non

Non

Non

Non

(1) n'accepte pas les constructions avec n PRECEDING ou n FOLLOWING

Fonctions spécifiques aux éditeurs :

RATIO_TO_REPORT

CORR

FIRST/LAST

COVAR…

REGR…

IBM DB2 9

Oui

Oui

Non

Non

Non

Oracle 11G

Oui

Oui

Oui

Oui

Oui

SQL Server 2008

Non

Non

Non

Non

Non

Sybase ASA

Non

Non

Non

Oui

Oui

PostGreSQL 8.4

Oui

Oui

Oui

Oui

Oui

En matière de fonctions fenêtrées le SGBDR le plus pointu est actuellement Oracle malgré qu'il n'implémente pas la clause WINDOW.

IBM DB2, PostGreSQL et Sybase ASA arrivent en second avec quelques lacunes dont la principale pour PostGreSQL est de ne pas permettre de limiter la fenêtre à un nombre déterminé de lignes.

Il est dommage de constater que MS SQL Server est à la traîne derrière Oracle, PostGreSQL Sybase AS A et IBM DB2.

Les plus nuls au véritable sens du terme sont MySQL, FireBird et Acces qui ne connaissent pas du tout ces concepts !

XI - ANNEXE D - informations complémentaires

Livres :

- SQL - collections Synthèse de cours et exercices - F. Brouard, C. Soutou, R Bruchez - Pearson Education 2008

- SQL par l'exemple - Anthony Molinaro - O'Reilly 2007

- Advanced SQL:1999 - Jim Melton - Morgan Kaufmann 2003

Web :

comparaison

http://www.postgresonline.com/journal/index.php?/archives/122-Window-Functions-Comparison-Between-PostgreSQL-8.4,-SQL-Server-2008,-Oracle,-IBM-DB2.html

DB2

http://publib.boulder.ibm.com/infocenter/rbhelp/v6r3/index.jsp?topic=/com.ibm.redbrick.doc6.3/sqlrg/sqlrg35.htmhttp://www.redbooks.ibm.com/redbooks/pdfs/sg246546.pdf

Oracle

http://lalystar.developpez.com/fonctionsAnalytiques/http://www.isnetne.ch/isnet29/documentation/ISNet29_SQL_aggregation.pdf

SQL Server

http://www.solidq.com/insidetsql/over_clause_and_ordered_calculations.doc

PostGreSQL

http://wiki.postgresql.org/wiki/SQL2003_widowing_querieshttp://docs.postgresqlfr.org/8.4/sql-expressions.html#syntax-window-functions

Sybase ASA

http://www.ianywhere.com/developer/product_manuals/sqlanywhere/1000/en/html/dbugen10/ug-ug-olap-s-5122634.html

MySQL

http://www.oreillynet.com/pub/a/mysql/2007/03/29/emulating-analytic-aka-ranking-functions-with-mysql.html

XII - ANNEXE E - fichier SQL pour les exemples

FonctionFenetrageOLAP.txt