by Markus Winand.

Using Window Functions for Efficient Pagination


Window functions offer yet another way to implement pagination in SQL. This is a flexible, and above all, standards-compliant method. However, only SQL Server, the Oracle database and PostgreSQL 15+ can use them for a pipelined top-N query. MySQL, MariaDB0and Db2 (LUW) do not abort the index scan after fetching enough rows and therefore execute these queries very inefficiently.

The following example uses the window function ROW_NUMBER for a pagination query:

SELECT *
  FROM ( SELECT sales.*
              , ROW_NUMBER() OVER (ORDER BY sale_date DESC
                                          , sale_id   DESC) rn
           FROM sales
       ) tmp
 WHERE rn between 11 and 20
 ORDER BY sale_date DESC, sale_id DESC

The ROW_NUMBER function enumerates the rows according to the sort order defined in the over clause. The outer where clause uses this enumeration to limit the result to the second page (rows 11 through 20).

If you like this page, you might also like …

… to subscribe my mailing lists, get free stickers, buy my book or join a training.

The Oracle database recognizes the abort condition and uses the index on SALE_DATE and SALE_ID to produce a pipelined top-N behavior:

The WINDOW NOSORT STOPKEY operation indicates that there is no sort operation (NOSORT) and that the database aborts the execution when reaching the upper threshold (STOPKEY). Considering that the aborted operations are executed in a pipelined manner, it means that this query is as efficient as the offset method explained in the previous section.

Support of this optimization is by no means common among SQL products.

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. Only with row_number()
  2. Not with partition by clause (see below)

While this optimization could conceptually work with any monotonic function, there is a clear focus on the ROW_NUMBER function among the analyzed implementations.

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

Caution needs to be taken when using partition by: Even if the where clause limits the rows to a single partition, the pure presence of partition by disables this optimization in some products. This might happen if the partitioned window function is part of a view, but the outer query limits the view to a single partition.

MariaDB 12.0.2MySQL 9.4.0Oracle DB 23.9aPostgreSQL 17SQL Server 2022OVER(ORDER BY…)OVER(ORDER BY…) + WHERE x=?OVER(PARTITION BY x ORDER BY…) + WHERE x=?
  1. Not all functions that work in presence of a where clause

The strength of window functions is not pagination, however, but analytical calculations. If you have never used window functions before, you should definitely spend a few hours studying the respective documentation.

Previous pageNext page

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.

About the Author

Photo of Markus Winand

Markus Winand provides insights into SQL and shows how different systems support it at modern-sql.com. Previously he made use-the-index-luke.com, which is still actively maintained. Markus can be hired as trainer, speaker and consultant via winand.at.

Buy the Book

Cover of “SQL Performance Explained”: Squirrel running on grass

The essence of SQL tuning in 200 pages

Buy now!
(paperback and/or PDF)

Paperback also available at Amazon.com.

Hire Markus

Markus offers SQL training and consulting for developers working at companies of all sizes.
Learn more »

Footnotes

  1. MySQL supports window functions since version 8.0, MariaDB since 10.2.

Connect with Markus Winand

Subscribe mailinglistsSubscribe the RSS feedMarkus Winand on LinkedInMarkus Winand on XINGMarkus Winand on TwitterMarkus Winand on Bluesky
Copyright 2010-2025 Markus Winand. All righs reserved.
Legal | Contact | NO WARRANTY | Trademarks | Privacy and GDPR