LIMIT 10..20 in SQL Server
Asked Answered
C

16

180

I'm trying to do something like :

SELECT * FROM table LIMIT 10,20

or

SELECT * FROM table LIMIT 10 OFFSET 10

but using SQL Server

The only solution I found looks like overkill:

SELECT * FROM ( 
  SELECT *, ROW_NUMBER() OVER (ORDER BY name) as row FROM sys.databases 
 ) a WHERE row > 5 and row <= 10

I also found:

SELECT TOP 10 * FROM stuff; 

... but it's not what I want to do since I can't specify the starting limit.

Is there another way for me to do that ?

Also, just curious, is there a reason why doesn't SQL Server support the LIMIT function or something similar? I don't want to be mean, but that really sounds like something a DBMS needs ... If it does, then I'm sorry for being so ignorant! I've been working with MySQL and SQL+ for the past 5 years so...

Carboy answered 9/6, 2009 at 19:15 Comment(3)
Using a CTE for ROW_NUMBER() and limiting with TOP for the width of the range and a WHERE condition for a bound of the range is best I've been able to achieve. I've also noticed much better performance if the TOP clause uses a literal instead of variableSporocyte
The problem with any solution involving the ROW_NUMBER() is that if you don't know in advance what columns you'll have, and you have joins, and the joined tables have the same column name, you'll get a "The column 'xxx' was specified multiple times". This isn't as uncommon as it might initially sound. I use Dapper, and my tables all have an Id column. Dapper splits and maps on that, so I don't want to rename them, but I can't use SELECT * FROM ([original query]) alias. I've not yet figured out a solution!Koller
Possible duplicate of How to implement LIMIT with Microsoft SQL Server?Oxidize
M
117

The LIMIT clause is not part of standard SQL. It's supported as a vendor extension to SQL by MySQL, PostgreSQL, and SQLite.

Other brands of database may have similar features (e.g. TOP in Microsoft SQL Server), but these don't always work identically.

It's hard to use TOP in Microsoft SQL Server to mimic the LIMIT clause. There are cases where it just doesn't work.

The solution you showed, using ROW_NUMBER() is available in Microsoft SQL Server 2005 and later. This is the best solution (for now) that works solely as part of the query.

Another solution is to use TOP to fetch the first count + offset rows, and then use the API to seek past the first offset rows.

See also:

Mastoiditis answered 9/6, 2009 at 19:34 Comment(0)
S
153

For SQL Server 2012 + you can use.

SELECT  *
FROM     sys.databases
ORDER BY name 
OFFSET  5 ROWS 
FETCH NEXT 5 ROWS ONLY 
Shy answered 11/2, 2012 at 16:46 Comment(8)
SQl Server 2012 require to specify ORDER BY when you use OFFSET 5 ROWS FETCH NEXT 5 ROWS ONLY while MySql and SQLite doesn't require ORDER BY when you use LIMIT 5,5Fotheringhay
@qub1n - MySQL doesn't guarantee what rows you get back in that case though.Shy
Do you have to use offset, or can you leave that line out (assuming you don't want an offset)?Hoard
@Hoard - OFFSET clause is mandatory with FETCH. You can never use, ORDER BY … FETCH. - so you need OFFSET 0 ROWS Shy
You example query runs fine but If I change the table name and order by col as below SELECT * FROM DimProduct ORDER BY ProductKey OFFSET 5 ROWS FETCH NEXT 5 ROWS ONLY It gives error Parse error at line: 4, column: 1: Incorrect syntax near 'OFFSET'Earth
@Earth - sounds like you are on a version of SQL Server that does not support this (or database compatibility level). This is introduced in 2012Shy
@MartinSmith SELECT compatibility_level FROM sys.databases WHERE name = 'MyDbName' returns 130 which means SQL Server 2016. It is a SQL Server DW instance on Azure if it makes any different.Earth
@MartinSmith FYI you do get predictable results without ordering because the MySQL query engine scans tables based on entry point. The only thing you can't know is if your limit is greater than the known existing table size (i.e. catching new/hot inserts) then you can't know what you'll get. However, if you hit the same table with an unsorted LIMIT x,x clause, you'll get the same records back every time. This might carp out on you through bad replication nodes though...Brinn
M
117

The LIMIT clause is not part of standard SQL. It's supported as a vendor extension to SQL by MySQL, PostgreSQL, and SQLite.

Other brands of database may have similar features (e.g. TOP in Microsoft SQL Server), but these don't always work identically.

It's hard to use TOP in Microsoft SQL Server to mimic the LIMIT clause. There are cases where it just doesn't work.

The solution you showed, using ROW_NUMBER() is available in Microsoft SQL Server 2005 and later. This is the best solution (for now) that works solely as part of the query.

Another solution is to use TOP to fetch the first count + offset rows, and then use the API to seek past the first offset rows.

See also:

Mastoiditis answered 9/6, 2009 at 19:34 Comment(0)
M
37

as you found, this is the preferred sql server method:

SELECT * FROM ( 
  SELECT *, ROW_NUMBER() OVER (ORDER BY name) as row FROM sys.databases 
 ) a WHERE a.row > 5 and a.row <= 10
Mentalist answered 9/6, 2009 at 19:19 Comment(4)
Why the a after the inner select? I assume you are giving the inner select an alias, but then you never seem to use it... Should you then do a.row instead of just row?Damicke
@Lucas, you are required to put an alias after the ( ) derived table, but it will let it go if you then forget to use it to refer to the columns. I fixed it though...Mentalist
thanks, i found that out the hard way (tried to leave the alias out).Damicke
Voted +1: However, @MartinSmith 's answer is voted more, after comparing execution plan with this approach's, I found out that, this solution works way faster.Germanism
F
13

How about this?

SET ROWCOUNT 10 

SELECT TOP 20 *
FROM sys.databases
ORDER BY database_id DESC

It gives you the last 10 rows of the first 20 rows. One drawback is that the order is reversed, but, at least it's easy to remember.

Fontes answered 9/6, 2009 at 19:33 Comment(1)
What if there are only 14 rows in the table? You get rows 14 down to 5, which is not the same as rows returned by LIMIT 10 OFFSET 10 (should be rows 14 down to 11).Mastoiditis
F
10

Unfortunately, the ROW_NUMBER() is the best you can do. It's actually more correct, because the results of a limit or top clause don't really have meaning without respect to some specific order. But it's still a pain to do.

Update: Sql Server 2012 adds a limit -like feature via OFFSET and FETCH keywords. This is the ansi-standard approach, as opposed to LIMIT, which is a non-standard MySql extension.

Frame answered 9/6, 2009 at 19:19 Comment(8)
@Joel: Can you explain why ROW_NUMBER() is unable to number the rows the way they come out of ORDER BY? I've always wondered why the "OVER (ORDER BY name)" is mandatory, but I guess there is a good reason for it. Or at least a reason.Deprived
because there is no such thing as order without an order by clause. You get whatever order the records were available to the server, and that could change from query request to query request.Frame
But I was explicitly referring to an ORDER BY clause. Assume the query itself has one - why can't ROW_NUMBER() just just that? I mean, what's the DB-internal reason that forces me to change the query in two places if I want to get a slice of it in a different order?Deprived
Interesting. Do you know if Microsoft plans on including the Limit function a release to come of sqlserver?Carboy
@marcgg: I've never read any indication that Microsoft plans to implement LIMIT. Even if they do have such a plan, closed-source vendors tend not to pre-announce features. It would certainly be a helpful feature, but we don't know how much work it would be to implement, given their code.Mastoiditis
If you don't want to repeat yourself in the ORDER BY clause, use the ROW_NUMBER() alias rather than the original set of columns.Deceptive
@Tomalak: As far as SQL Server is concerned, the ordering used to calculate ROW_NUMBER() is completely unrelated to the ordering of the resultset. That's why you have to specify them separately.Beedon
if you wanted to throw caution to the wind, you could try OVER ( ORDER BY (SELECT NULL) ) if you were set on not specifying a specific order for the windowLaktasic
S
10

If you are using SQL Server 2012+ vote for Martin Smith's answer and use the OFFSET and FETCH NEXT extensions to ORDER BY,

If you are unfortunate enough to be stuck with an earlier version, you could do something like this,

WITH Rows AS
(
    SELECT
              ROW_NUMBER() OVER (ORDER BY [dbo].[SomeColumn]) [Row]
            , *
        FROM
              [dbo].[SomeTable]
)
SELECT TOP 10
          *
     FROM
         Rows
    WHERE Row > 10

I believe is functionaly equivalent to

SELECT * FROM SomeTable LIMIT 10 OFFSET 10 ORDER BY SomeColumn

and the best performing way I know of doing it in TSQL, before MS SQL 2012.


If there are very many rows you may get better performance using a temp table instead of a CTE.

Sporocyte answered 4/7, 2012 at 15:27 Comment(1)
Upvoted for pointing out Martin Smith's answer (and linking to it) while providing a pre-2012 solution. Also for the temp table advice because you are correct :)Ezmeralda
P
6
SELECT TOP 10 *
FROM TABLE
WHERE IDCOLUMN NOT IN (SELECT TOP 10 IDCOLUMN FROM TABLE)

Should give records 11-20. Probably not too efficient if incrementing to get further pages, and not sure how it might be affected by ordering. Might have to specify this in both WHERE statements.

Patmos answered 10/1, 2011 at 15:46 Comment(1)
You always need an ORDER BY to guarantee order. You have two TOP in this so you need two ORDER BYYellowstone
E
2

A good way is to create a procedure:

create proc pagination (@startfrom int ,@endto int) as
SELECT * FROM ( 
  SELECT *, ROW_NUMBER() OVER (ORDER BY name desc) as row FROM sys.databases 
 ) a WHERE a.row > @startfrom and a.row <= @endto

just like limit 0,2 /////////////// execute pagination 0,4

Eames answered 28/11, 2013 at 14:38 Comment(0)
T
1

Just for the record solution that works across most database engines though might not be the most efficient:

Select Top (ReturnCount) *
From (
    Select Top (SkipCount + ReturnCount) *
    From SourceTable
    Order By ReverseSortCondition
) ReverseSorted
Order By SortCondition

Pelase note: the last page would still contain ReturnCount rows no matter what SkipCount is. But that might be a good thing in many cases.

Tropopause answered 1/3, 2016 at 10:52 Comment(0)
C
1

The equivalent of LIMIT is SET ROWCOUNT, but if you want generic pagination it's better to write a query like this:

;WITH Results_CTE AS
(
    SELECT
        Col1, Col2, ...,
        ROW_NUMBER() OVER (ORDER BY SortCol1, SortCol2, ...) AS RowNum
    FROM Table
    WHERE <whatever>
)
SELECT *
FROM Results_CTE
WHERE RowNum >= @Offset
AND RowNum < @Offset + @Limit
Cystocarp answered 12/4, 2016 at 9:21 Comment(0)
S
0
select * from (select id,name,ROW_NUMBER() OVER (ORDER BY id  asc) as row
from tableName1) tbl1
where tbl1.row>=10 and tbl1.row<=15

Will print rows from 10 to 15.

Silurid answered 22/1, 2013 at 7:20 Comment(0)
E
0

So far this format is what is working for me (not the best performance though):

SELECT TOP {desired amount of rows} * 
FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY {order columns} asc)__row__ FROM {table})tmp
WHERE __row__ > {offset row count}

A note on the side, paginating over dynamic data can lead to strange/unexpected results.

Earthward answered 9/8, 2013 at 5:8 Comment(0)
T
0

From the MS SQL Server online documentation (http://technet.microsoft.com/en-us/library/ms186734.aspx ), here is their example that I have tested and works, for retrieving a specific set of rows. ROW_NUMBER requires an OVER, but you can order by whatever you like:

WITH OrderedOrders AS
(
  SELECT SalesOrderID, OrderDate,
  ROW_NUMBER() OVER (ORDER BY OrderDate) AS RowNumber
  FROM Sales.SalesOrderHeader 
) 
SELECT SalesOrderID, OrderDate, RowNumber  
FROM OrderedOrders 
WHERE RowNumber BETWEEN 50 AND 60;
Timeout answered 8/10, 2013 at 20:15 Comment(0)
M
0

Use all SQL server: ;with tbl as (SELECT ROW_NUMBER() over(order by(select 1)) as RowIndex,* from table) select top 10 * from tbl where RowIndex>=10

Moil answered 10/1, 2015 at 6:6 Comment(0)
L
0

You could use LEAD(expression,offset) , but this doesn't work with SELECT *. In addition to forcing your column collection to be verbose, this method will also end up with the potential to return more rows than there are results - filling in not-found rows with default values such as NULL.


SELECT TOP(5)
  LEAD( d.name                                                  , 5 ) OVER( ORDER BY d.name ASC )  [name]
, LEAD( d.database_id                                           , 5 ) OVER( ORDER BY d.name ASC )  [database_id]
, LEAD( d.source_database_id                                    , 5 ) OVER( ORDER BY d.name ASC )  [source_database_id]
, LEAD( d.owner_sid                                             , 5 ) OVER( ORDER BY d.name ASC )  [owner_sid]
, LEAD( d.create_date                                           , 5 ) OVER( ORDER BY d.name ASC )  [create_date]
, LEAD( d.compatibility_level                                   , 5 ) OVER( ORDER BY d.name ASC )  [compatibility_level]
, LEAD( d.collation_name                                        , 5 ) OVER( ORDER BY d.name ASC )  [collation_name]
, LEAD( d.user_access                                           , 5 ) OVER( ORDER BY d.name ASC )  [user_access]
, LEAD( d.user_access_desc                                      , 5 ) OVER( ORDER BY d.name ASC )  [user_access_desc]
, LEAD( d.is_read_only                                          , 5 ) OVER( ORDER BY d.name ASC )  [is_read_only]
, LEAD( d.is_auto_close_on                                      , 5 ) OVER( ORDER BY d.name ASC )  [is_auto_close_on]
, LEAD( d.is_auto_shrink_on                                     , 5 ) OVER( ORDER BY d.name ASC )  [is_auto_shrink_on]
, LEAD( d.state                                                 , 5 ) OVER( ORDER BY d.name ASC )  [state]
, LEAD( d.state_desc                                            , 5 ) OVER( ORDER BY d.name ASC )  [state_desc]
, LEAD( d.is_in_standby                                         , 5 ) OVER( ORDER BY d.name ASC )  [is_in_standby]
, LEAD( d.is_cleanly_shutdown                                   , 5 ) OVER( ORDER BY d.name ASC )  [is_cleanly_shutdown]
, LEAD( d.is_supplemental_logging_enabled                       , 5 ) OVER( ORDER BY d.name ASC )  [is_supplemental_logging_enabled]
, LEAD( d.snapshot_isolation_state                              , 5 ) OVER( ORDER BY d.name ASC )  [snapshot_isolation_state]
, LEAD( d.snapshot_isolation_state_desc                         , 5 ) OVER( ORDER BY d.name ASC )  [snapshot_isolation_state_desc]
, LEAD( d.is_read_committed_snapshot_on                         , 5 ) OVER( ORDER BY d.name ASC )  [is_read_committed_snapshot_on]
, LEAD( d.recovery_model                                        , 5 ) OVER( ORDER BY d.name ASC )  [recovery_model]
, LEAD( d.recovery_model_desc                                   , 5 ) OVER( ORDER BY d.name ASC )  [recovery_model_desc]
, LEAD( d.page_verify_option                                    , 5 ) OVER( ORDER BY d.name ASC )  [page_verify_option]
, LEAD( d.page_verify_option_desc                               , 5 ) OVER( ORDER BY d.name ASC )  [page_verify_option_desc]
, LEAD( d.is_auto_create_stats_on                               , 5 ) OVER( ORDER BY d.name ASC )  [is_auto_create_stats_on]
, LEAD( d.is_auto_create_stats_incremental_on                   , 5 ) OVER( ORDER BY d.name ASC )  [is_auto_create_stats_incremental_on]
, LEAD( d.is_auto_update_stats_on                               , 5 ) OVER( ORDER BY d.name ASC )  [is_auto_update_stats_on]
, LEAD( d.is_auto_update_stats_async_on                         , 5 ) OVER( ORDER BY d.name ASC )  [is_auto_update_stats_async_on]
, LEAD( d.is_ansi_null_default_on                               , 5 ) OVER( ORDER BY d.name ASC )  [is_ansi_null_default_on]
, LEAD( d.is_ansi_nulls_on                                      , 5 ) OVER( ORDER BY d.name ASC )  [is_ansi_nulls_on]
, LEAD( d.is_ansi_padding_on                                    , 5 ) OVER( ORDER BY d.name ASC )  [is_ansi_padding_on]
, LEAD( d.is_ansi_warnings_on                                   , 5 ) OVER( ORDER BY d.name ASC )  [is_ansi_warnings_on]
, LEAD( d.is_arithabort_on                                      , 5 ) OVER( ORDER BY d.name ASC )  [is_arithabort_on]
, LEAD( d.is_concat_null_yields_null_on                         , 5 ) OVER( ORDER BY d.name ASC )  [is_concat_null_yields_null_on]
, LEAD( d.is_numeric_roundabort_on                              , 5 ) OVER( ORDER BY d.name ASC )  [is_numeric_roundabort_on]
, LEAD( d.is_quoted_identifier_on                               , 5 ) OVER( ORDER BY d.name ASC )  [is_quoted_identifier_on]
, LEAD( d.is_recursive_triggers_on                              , 5 ) OVER( ORDER BY d.name ASC )  [is_recursive_triggers_on]
, LEAD( d.is_cursor_close_on_commit_on                          , 5 ) OVER( ORDER BY d.name ASC )  [is_cursor_close_on_commit_on]
, LEAD( d.is_local_cursor_default                               , 5 ) OVER( ORDER BY d.name ASC )  [is_local_cursor_default]
, LEAD( d.is_fulltext_enabled                                   , 5 ) OVER( ORDER BY d.name ASC )  [is_fulltext_enabled]
, LEAD( d.is_trustworthy_on                                     , 5 ) OVER( ORDER BY d.name ASC )  [is_trustworthy_on]
, LEAD( d.is_db_chaining_on                                     , 5 ) OVER( ORDER BY d.name ASC )  [is_db_chaining_on]
, LEAD( d.is_parameterization_forced                            , 5 ) OVER( ORDER BY d.name ASC )  [is_parameterization_forced]
, LEAD( d.is_master_key_encrypted_by_server                     , 5 ) OVER( ORDER BY d.name ASC )  [is_master_key_encrypted_by_server]
, LEAD( d.is_query_store_on                                     , 5 ) OVER( ORDER BY d.name ASC )  [is_query_store_on]
, LEAD( d.is_published                                          , 5 ) OVER( ORDER BY d.name ASC )  [is_published]
, LEAD( d.is_subscribed                                         , 5 ) OVER( ORDER BY d.name ASC )  [is_subscribed]
, LEAD( d.is_merge_published                                    , 5 ) OVER( ORDER BY d.name ASC )  [is_merge_published]
, LEAD( d.is_distributor                                        , 5 ) OVER( ORDER BY d.name ASC )  [is_distributor]
, LEAD( d.is_sync_with_backup                                   , 5 ) OVER( ORDER BY d.name ASC )  [is_sync_with_backup]
, LEAD( d.service_broker_guid                                   , 5 ) OVER( ORDER BY d.name ASC )  [service_broker_guid]
, LEAD( d.is_broker_enabled                                     , 5 ) OVER( ORDER BY d.name ASC )  [is_broker_enabled]
, LEAD( d.log_reuse_wait                                        , 5 ) OVER( ORDER BY d.name ASC )  [log_reuse_wait]
, LEAD( d.log_reuse_wait_desc                                   , 5 ) OVER( ORDER BY d.name ASC )  [log_reuse_wait_desc]
, LEAD( d.is_date_correlation_on                                , 5 ) OVER( ORDER BY d.name ASC )  [is_date_correlation_on]
, LEAD( d.is_cdc_enabled                                        , 5 ) OVER( ORDER BY d.name ASC )  [is_cdc_enabled]
, LEAD( d.is_encrypted                                          , 5 ) OVER( ORDER BY d.name ASC )  [is_encrypted]
, LEAD( d.is_honor_broker_priority_on                           , 5 ) OVER( ORDER BY d.name ASC )  [is_honor_broker_priority_on]
, LEAD( d.replica_id                                            , 5 ) OVER( ORDER BY d.name ASC )  [replica_id]
, LEAD( d.group_database_id                                     , 5 ) OVER( ORDER BY d.name ASC )  [group_database_id]
, LEAD( d.resource_pool_id                                      , 5 ) OVER( ORDER BY d.name ASC )  [resource_pool_id]
, LEAD( d.default_language_lcid                                 , 5 ) OVER( ORDER BY d.name ASC )  [default_language_lcid]
, LEAD( d.default_language_name                                 , 5 ) OVER( ORDER BY d.name ASC )  [default_language_name]
, LEAD( d.default_fulltext_language_lcid                        , 5 ) OVER( ORDER BY d.name ASC )  [default_fulltext_language_lcid]
, LEAD( d.default_fulltext_language_name                        , 5 ) OVER( ORDER BY d.name ASC )  [default_fulltext_language_name]
, LEAD( d.is_nested_triggers_on                                 , 5 ) OVER( ORDER BY d.name ASC )  [is_nested_triggers_on]
, LEAD( d.is_transform_noise_words_on                           , 5 ) OVER( ORDER BY d.name ASC )  [is_transform_noise_words_on]
, LEAD( d.two_digit_year_cutoff                                 , 5 ) OVER( ORDER BY d.name ASC )  [two_digit_year_cutoff]
, LEAD( d.containment                                           , 5 ) OVER( ORDER BY d.name ASC )  [containment]
, LEAD( d.containment_desc                                      , 5 ) OVER( ORDER BY d.name ASC )  [containment_desc]
, LEAD( d.target_recovery_time_in_seconds                       , 5 ) OVER( ORDER BY d.name ASC )  [target_recovery_time_in_seconds]
, LEAD( d.delayed_durability                                    , 5 ) OVER( ORDER BY d.name ASC )  [delayed_durability]
, LEAD( d.delayed_durability_desc                               , 5 ) OVER( ORDER BY d.name ASC )  [delayed_durability_desc]
, LEAD( d.is_memory_optimized_elevate_to_snapshot_on            , 5 ) OVER( ORDER BY d.name ASC )  [is_memory_optimized_elevate_to_snapshot_on]
, LEAD( d.is_federation_member                                  , 5 ) OVER( ORDER BY d.name ASC )  [is_federation_member]
, LEAD( d.is_remote_data_archive_enabled                        , 5 ) OVER( ORDER BY d.name ASC )  [is_remote_data_archive_enabled]
, LEAD( d.is_mixed_page_allocation_on                           , 5 ) OVER( ORDER BY d.name ASC )  [is_mixed_page_allocation_on]
FROM     sys.databases d
ORDER BY d.name  asc

As you can see, being a "Function", this also requires re-aliasing column names as well.

Laktasic answered 13/6, 2023 at 21:8 Comment(0)
S
-3
 SELECT * FROM users WHERE Id Between 15 and 25

it will print from 15 to 25 as like limit in MYSQl

Shabbir answered 5/6, 2015 at 10:55 Comment(1)
What if user deleted a record between 15 and 25?Eclampsia

© 2022 - 2025 — McMap. All rights reserved.