Retrieve TOP 10 Rows Without Using TOP or LIMIT? – Interview Question of the Week #247

Question: How do you retrieve ten rows without TOP or LIMIT? Number the rows with ROW_NUMBER, then filter the numbered result in an outer query.

The beginning of an ordered line of pebbles occupies a separate tray compartment

When a health-check client asked this, my first reaction was, “Why reinvent the wheel?” Their reason was sensible: they were writing software for more than one database engine, and TOP and LIMIT are different dialect choices.

SELECT ROUTINE_SCHEMA, ROUTINE_NAME, ROUTINE_DEFINITION
FROM (
    SELECT ROUTINE_SCHEMA, ROUTINE_NAME, ROUTINE_DEFINITION,
           ROW_NUMBER() OVER (ORDER BY ROUTINE_SCHEMA, ROUTINE_NAME) AS ROWNUM
    FROM INFORMATION_SCHEMA.ROUTINES
    WHERE ROUTINE_TYPE = 'PROCEDURE'
) AS T
WHERE ROWNUM <= 10
ORDER BY ROWNUM;

This keeps the original example, listing the first ten stored procedures from INFORMATION_SCHEMA.ROUTINES. I added the schema to the ordering so same-named procedures in different schemas don’t tie. The final ORDER BY also matters: the window’s ordering assigns numbers but doesn’t guarantee the displayed order.

Ten rows means at most ten. If fewer procedures are visible, fewer rows are returned. These are the first names in the chosen ordering, not the ten most important or most frequently executed procedures.

The row-number pattern is broadly useful across modern database engines, but the original claim that this exact query works on every recent database was too broad. Window-function availability depends on version, and catalog columns and permissions differ. For example, SQL Server’s ROUTINE_DEFINITION is limited to 4,000 characters and is not a complete procedure-export tool.

Use this pattern when its portability helps your application. It isn’t automatically faster than TOP, and a different engine still needs its own verification. The interview lesson is the outer filter: a window result is calculated too late to use its alias in the same SELECT’s WHERE clause.

Related: recognizing a forced index and fragmentation with row counts.

Published by Pinal Dave on SQLAuthority. More of my work at pinaldave.com.


Discover more from SQL Authority with Pinal Dave

Subscribe to get the latest posts sent to your email.

MariaDB, MySQL, PostgreSQL, SQL Server
Previous Post
How to Know If Index is Forced on Query? – Interview Question of the Week #246
Next Post
Why Query Store Actual Operation Mode is Not Same as Requested? – Interview Question of the Week #248

Related Posts

3 Comments. Leave new

  • Hi Pinal,

    Although I think it’s a good idea to confirm to a certain standard, your solution gives a different and worse execution plan in SQL.
    I tested it with an enlarged AdventureWorks2016 database (SQL2017 developer) using a select from view vSalesPersonSalesByFiscalYears. The first run was done using TOP(100) and the 2nd run was using the ROW_NUMBER() solution.
    For this relatively small result set the cost for using TOP was 2,32761 and for the ROW_NUMBER() it was 2,68877. This is an increase by 15%. I also tested it on one of our large acceptance databases (SQL2012 enterprise) and the result was almost 20% slower using ROW_NUMBER()

    So you pay a price if you want to stick to a ‘generic’ query. I prefer a maximum performance.

    Reply
    • Hi Wilfred,

      Thanks for the test. Yes that would happen.

      Just so you know this post was not about tuning the query but rather wanted a query that would work some of the major databases. Additionally, the TOP has different breakpoint when it is about performance, I will write a blog post about it in the future.

      Reply
  • Materialization of whole resultset and then filtering will be a problem. For a interview this might be ok.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.