par Guillaume Lelarge

Utiliser les fonctions de fenêtrage pour une pagination efficace


Les fonctions de fenêtrage offrent un autre moyen d'implémenter la pagination en SQL. C'est une méthode flexible et, par-dessus tout, c'est une méthode proposée par le standard. Néanmoins, seuls SQL Server, Oracle et PostgreSQL (à partir de la version 15 pour ce dernier) peuvent les utiliser pour une requête top-N en pipeline. MySQL, MariaDB0, et Db2 (LUW) n'annulent pas le parcours d'index après avoir récupéré les lignes nécessaires, ce qui implique qu'ils ne peuvent donc pas les exécuter de façon efficace.

L'exemple suivant utilise la fonction de fenêtrage ROW_NUMBER pour une requête de pagination :

SELECT *
  FROM ( SELECT ventes.*
              , ROW_NUMBER() OVER (ORDER BY date_vente DESC
                                          , id_vente   DESC) rn
           FROM ventes
       ) tmp
 WHERE rn between 11 and 20
 ORDER BY date_vente DESC, id_vente DESC;

La fonction ROW_NUMBER énumère les lignes suivant l'ordre de tri défini dans la clause over. La clause where externe utilise cette énumération pour limiter les résultats à la seconde page (lignes 11 à 20).

La base de données Oracle reconnaît la condition d'annulation et utilise l'index sur DATE_VENTE et ID_VENTE pour produire un comportement du type requête top-N en pipeline :

Oracle

---------------------------------------------------------------
|Id | Operation                      | Name    | Rows |  Cost |
---------------------------------------------------------------
| 0 | SELECT STATEMENT               |         | 1004K| 36877 |
|*1 |  VIEW                          |         | 1004K| 36877 |
|*2 |   WINDOW NOSORT STOPKEY        |         | 1004K| 36877 |
| 3 |    TABLE ACCESS BY INDEX ROWID | VENTES  | 1004K| 36877 |
| 4 |     INDEX FULL SCAN DESCENDING | VT_DTID | 1004K|  2955 |
---------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("RN">=11 AND "RN"<=20)
2 - filter(ROW_NUMBER() OVER (
           ORDER BY "DATE_VENTE" DESC, "ID_VENTE" DESC ) <=20)

PostgreSQL

Depuis la version 15, le plan d'exécution affiche la « Run Condition », qui peut annuler l'exécution du reste.


                     QUERY PLAN
-------------------------------------------------------
 Subquery Scan on tmp
    (cost=0.42..141724.98 rows=334751 width=249)
    (actual time=0.040..0.052 rows=10 loops=1)
 Filter: (tmp.rn >= 11)
 Rows Removed by Filter: 10
 Buffers: shared hit=5
 -> WindowAgg
       (cost=0.42..129171.80 rows=1004254 width=249)
       (actual time=0.028..0.049 rows=20 loops=1)
    Run Condition: (row_number() OVER (?) <= 20)
    Buffers: shared hit=5
    -> Index Scan Backward using sl_dtid on sales
           (cost=0.42..111597.36 rows=1004254 width=241)
           (actual time=0.018..0.025 rows=22 loops=1)
       Buffers: shared hit=5

L'opération WINDOW NOSORT STOPKEY indique qu'il n'y a pas d'opération de tri (NOSORT) et que la base de données annule l'exécution une fois qu'elle a atteint la limite supérieure (STOPKEY). En considérant que les opérations annulées ont été exécutées sous la forme d'un pipeline, cela signifie que cette requête est aussi efficace que la méthode de décalage expliquée dans la section précédente.

Le support de cette optimisation n'est pas du tout commun parmi les produits SQL.

MariaDBMySQLOracle DBPostgreSQLSQL Server200720092011201320152017201920212023⚠ 2008R2 - 2022a✓ 17+b⚠ 15 - 16b⊘ 9.0 - 14⚠ 12cR1 - 23.9b⚠ 11gR1 - 11gR2⊘ 8.0.18 - 9.4.0⊘ 10.2 - 12.0.2
  1. Seulement avec row_number()
  2. Sans la clause partition by (voir ci-dessous)

Bien que cette optimisation pourrait fonctionner conceptuellement avec toute fonction monotone, il y a clairement un focus sur la fonction ROW_NUMBER parmi les implémentations analysées.

MariaDB 12.0.2MySQL 9.4.0Oracle DB 23.9aPostgreSQL 17SQL Server 2022 ROW_NUMBER() RANK() DENSE_RANK() COUNT(…)
  1. Seulement avec la clause where : OVER(ORDER BY…) + WHERE x=?

Il est nécessaire de faire attention lors de l'utilisation de partition by : même si la clause where limite les lignes à une seule partition, la seule présence de partition by désactive cette optimisation dans certains produits. Ceci peut arriver si la fonction de fenêtrage partitionnée fait partie d'une vue, mais que la requête englobante limite la vue à une seule partition.

MariaDB 12.0.2MySQL 9.4.0Oracle DB 23.9aPostgreSQL 17SQL Server 2022 OVER(ORDER BY…) OVER(ORDER BY…) + WHERE x=?OVER(PARTITION BY x ORDER BY…) + WHERE x=?
  1. Pas toutes les fonctions qui travaillent en présence d'une clause where

Néanmoins, la force des fonctions de fenêtrage n'est pas la pagination mais les calculs analytiques. Si vous n'avez jamais utilisé de fonctions de fenêtrage auparavant, vous devriez passer quelques heures à étudier la documentation relative.

Section précédenteSection suivante

You can’t learn everything in one day. Subscribe the newsletter via E-Mail, Bluesky or RSS to gradually catch up. Have a look at modern-⁠sql.com as well.

À propos de l'auteur

Photo de Markus Winand

Markus Winand est l’ambassadeur de la renaissance SQL. Il a pour mission d’initier les développeurs à l’évolution du SQL au 21ème siècle. Markus peut être engagé comme formateur, conférencier et consultant chez winand.at.

Livre de Markus

Couverture du livre « SQL : Au cœur des performances »

L'essence de SQL tuning dans 200 pages.

Acheter de Markus
(Livre de poche et PDF)

Footnotes

  1. MySQL accepte les fonctions de fenêtrage depuis la version 8.0, MariaDB depuis la version 10.2.

Entrer en contact avec Markus Winand

Listes de diffusion de Markus WinandFlux RSSMarkus Winand sur LinkedInMarkus Winand sur XINGMarkus Winand sur TwitterMarkus Winand sur Bluesky
Copyright 2013-2025 Guillaume Lelarge, Markus Winand. All righs reserved.
Mentions légales | Contact | NO WARRANTY | Marque déposée | Confidentialité et RGPD