What is the best way (performance wise) to paginate results in SQL Server 2000, 2005, 2008, 2012 if you also want to get the total number of results (before paginating)?

- 211,314
- 129
- 689
- 1,509

- 10,840
- 12
- 41
- 43
-
34I've always wondered why they didn't just support specifying an offset as part of TOP (like MySQL/Posgresql support with LIMIT/OFFSET). Eg, they could just have the syntax "SELECT TOP x,y ...." where x = number of rows, y = starting offset. It would also be backwards compatible. – gregmac Sep 20 '08 at 20:47
-
3hey, me too... sql's 2005 pagination implementation it's really so akward... – opensas Apr 27 '09 at 18:42
-
7@gregmac - Sql Server 2012 does have limit/offset now. – O.O Aug 31 '13 at 17:29
-
2The accepted solution does not show how it is the best way (performance wise). Any data backing it up on large data sets? – O.O Aug 31 '13 at 17:32
-
3@O.O: A good benchmark can be found here: http://www.4guysfromrolla.com/webtech/042606-1.shtml. However, the [seek method](http://stackoverflow.com/a/19609938/521799) will outperform any offset-based pagination. – Lukas Eder Oct 26 '13 at 17:55
-
I was going to leave this topic alone, because I figured a billion people were going to jump on it, but it wasn't as busy a thread as I thought it would be. There are some articles on using row number and the BETWEEN statement to efficiently do pagination. [http://www.codeproject.com/KB/database/row_number.aspx](http://www.codeproject.com/KB/database/row_number.aspx) [http://www.singingeels.com/Articles/Pagination_In_SQL_Server_2005.aspx](http://www.singingeels.com/Articles/Pagination_In_SQL_Server_2005.aspx) and to kind of fake row numbers in sql server 2000 this link should give you somethin – stephenbayer Sep 20 '08 at 20:37
-
this example work me. https://stackoverflow.com/posts/9328808/revisions – Cristian Agudelo Jun 16 '18 at 01:22
-
This is a duplicate of [this question](https://stackoverflow.com/questions/548475/efficient-way-to-implement-paging/13991192#13991192) – d.popov Dec 03 '19 at 12:10
19 Answers
Finally, Microsoft SQL Server 2012 was released, I really like its simplicity for a pagination, you don't have to use complex queries like answered here.
For getting the next 10 rows just run this query:
SELECT * FROM TableName ORDER BY id OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY;
Key points to consider when using it:
ORDER BY
is mandatory to useOFFSET ... FETCH
clause.OFFSET
clause is mandatory withFETCH
. You cannot useORDER BY ... FETCH
.TOP
cannot be combined withOFFSET
andFETCH
in the same query expression.
-
14
-
1@BaconBits See this answer for a sneaky way of doing it with `FOR XML`: http://stackoverflow.com/a/273330/429949 – Richard Marskell - Drackir Jul 22 '15 at 15:48
-
2@RichardMarskell-Drackir There's lots of problems with `FOR XML PATH ('')`. First, it replaces XML control characters with XML entity codes. Hope you don't have `<`, `>`, or `&` in your data! Second, `FOR XML PATH ('')` used in this manner is actually undocumented syntax. You're supposed to specify a named column or an alternate element name. Doing neither is not in the doc, meaning the behavior is unreliable. Third, the more we accept the broken `FOR XML PATH ('')` syntax, the less likely it is that MS actually provides a *real* `LISTAGG() [ OVER() ]` function like they have needed to. – Bacon Bits Jul 22 '15 at 16:13
-
@BaconBits You can get around the encoding issue by using `FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')` as in the answer. Anyways, I was simply showing a workaround that's relatively efficient until they add the feature into the core of SQL Server. – Richard Marskell - Drackir Jul 22 '15 at 17:39
-
7shame the perf is so bad http://www.mssqlgirl.com/paging-function-performance-in-sql-server-2012.html – Jon Sep 08 '15 at 15:29
-
7@Jon, that linked blog post is not representative, in the sense it makes comparisons based on returning the page result by looking up values of the id column. – Zephyr was a Friend of Mine Nov 13 '15 at 11:38
-
mssqlgirl article might be interesting for some. Archived here for quick ref: https://web.archive.org/web/20190613101210/https://www.mssqlgirl.com/paging-function-performance-in-sql-server-2012.html – bendecko Feb 08 '23 at 17:42
Getting the total number of results and paginating are two different operations. For the sake of this example, let's assume that the query you're dealing with is
SELECT * FROM Orders WHERE OrderDate >= '1980-01-01' ORDER BY OrderDate
In this case, you would determine the total number of results using:
SELECT COUNT(*) FROM Orders WHERE OrderDate >= '1980-01-01'
...which may seem inefficient, but is actually pretty performant, assuming all indexes etc. are properly set up.
Next, to get actual results back in a paged fashion, the following query would be most efficient:
SELECT *
FROM ( SELECT ROW_NUMBER() OVER ( ORDER BY OrderDate ) AS RowNum, *
FROM Orders
WHERE OrderDate >= '1980-01-01'
) AS RowConstrainedResult
WHERE RowNum >= 1
AND RowNum < 20
ORDER BY RowNum
This will return rows 1-19 of the original query. The cool thing here, especially for web apps, is that you don't have to keep any state, except the row numbers to be returned.

- 52,000
- 11
- 64
- 62
-
44
-
7does this return all rows from the inner query & then filter based on outer query? for ex: inner query returns 100,000 & outer query returns only 20. – SoftwareGeek Jun 16 '11 at 03:59
-
4@SoftwareGeek: think of it as the subquery (inner query) returning a stream, which is then read until the outer WHERE clause is satisfied. How may rows are involved with that, depends entirely on the query, but the optimizer generally does a very good job on minimizing that number. Using the graphical execution plan viewer in SQL Server Management Studio (use Query/Include Actual Execution Plan) is very educational in that regard. – mdb Jun 16 '11 at 07:51
-
i am fine with this approach as long as it's not costing too much in terms of process. – SoftwareGeek Jun 20 '11 at 03:40
-
2ok, what if you get dublicated in inner select (like when you have inner join) how do you use distinct because RowNumber is different and it does not work – user217648 Aug 03 '12 at 12:18
-
please check out this link -> [link](http://stackoverflow.com/questions/548475/efficient-way-to-implement-paging) – Fredrick Gauss Nov 22 '12 at 14:11
-
@mdb - I see no proof that this is the best performance wise. All I see is one way of doing it. I've read that using TOP is actually more performant. – O.O Aug 31 '13 at 17:31
-
@mdb Is there any performance issue with the above query if we have more records ? please check my question http://stackoverflow.com/questions/19118532/pagination-in-sql-performance-issue – Peru Oct 03 '13 at 09:44
-
11Microsoft added a new feature to SQL 2012 that makes pagination similar to MySQL. Follow this link to learn how. It's an interesting article: http://dbadiaries.com/new-t-sql-features-in-sql-server-2012-offset-and-fetch – Arash Dec 20 '13 at 13:49
-
2This approach does not provide good performance (at least less than 1 second), if table contains lots of records i.e. > 3.000.000 and we are executing query which has "offset / rownum" near 3.000.000. I just tried with a simple table 'person (email PK, firstname, lastname)' containing 3.000.000 records but to fetch last records it takes 6 seconds with above query. I am using sql server 2008. – broadband Dec 27 '13 at 13:11
-
-
1is is better if we add `TOP 20` phrase to inner select query eg: `.. SELECT TOP 20 ROW_NUMBER() OVER ...` – S.Serpooshan Sep 02 '14 at 11:29
-
@saeedserpooshan If you do this, you will get only the first 20 results and nothing more past that. The actual pagination ocurs because the inner query returns the row numbers for you to choose from. – Ricardo Souza Jan 05 '15 at 21:28
-
-
3@broadband - if you have 3,000,000 rows & you want to page to the end, you need to re-think the legitimacy of your use case (I find it hard to believe a *user* would sit there paging through 3,000,000 rows). And if you actually have a legitimately unavoidable requirement that allows users to page to the end (which i doubt), then you shouldn't be implementing paging this naively, you should have an efficient cursored solution (not a sql cursor) that requires some sort of ephemeral positional state persistence, not the cookie cutter solution posted here that solves 99% of cases. – AaronHS Nov 27 '15 at 02:00
-
@AaronHS I agree that user won't do paging till the last page, but just wanted to point out performance issues regarding large number of records. In the end I solved it by paging with Id. `select top 100 from table where id > 35400` and remembered last Id and for next page: `select top 100 from table where id > 35515`. Why is Id 35515 and not 35400. Let's just say that some records got deleted between 35400 and 35515. With this approach I got of course the same performance if there are 10 or 10 milion records in the database. Id column is primary key and auto increment (identity in ms sql). – broadband Nov 30 '15 at 07:19
-
this method sucs when you need to filter the row constrained result deeper e.g where customerName like '%Chris%' it just returns a fraction of the desired results – Edwin O. Jan 06 '16 at 23:36
-
1if you can use primary key column in ROW_NUMBER() OVER ( ORDER BY PrimaryKeyCol ) , it will be the fastest . espacially in table with 1M rows . – bh_earth0 Jun 27 '16 at 06:23
-
`Getting the total number of results and paginating are two different operations.`: They don't have to be. Please see here: http://stackoverflow.com/a/14744475/109941 – Jim G. Mar 29 '17 at 17:06
-
How should the column, `x`, be determined in `OVER ( ORDER BY x)`? I'm guessing that you, @mdb, chose `OrderDate` since it provides an ordering. However, what if there's no natural ordering in a table? Thanks – Kevin Meredith Aug 10 '17 at 13:18
-
This solution returns all records regardless RowNum < 20. Something is not right. – usefulBee Mar 19 '18 at 20:47
-
Performance tip using TOP clause (assuming next code inside a stored procedure): `SELECT TOP (@PageSize) * FROM (SELECT TOP (@PageSize * @Page) ROW_NUMBER() OVER (ORDER BY OrderDate ) AS RowNum, * FROM Orders WHERE OrderDate >= '1980-01-01' ) AS RowConstrainedResult WHERE RowNum BETWEEN (@Page - 1) * @PageSize + 1 AND @Page * @PageSize ` – Sep 05 '18 at 13:10
Incredibly, no other answer has mentioned the fastest way to do pagination in all SQL Server versions. Offsets can be terribly slow for large page numbers as is benchmarked here. There is an entirely different, much faster way to perform pagination in SQL. This is often called the "seek method" or "keyset pagination" as described in this blog post here.
SELECT TOP 10 first_name, last_name, score, COUNT(*) OVER()
FROM players
WHERE (score < @previousScore)
OR (score = @previousScore AND player_id < @previousPlayerId)
ORDER BY score DESC, player_id DESC
The "seek predicate"
The @previousScore
and @previousPlayerId
values are the respective values of the last record from the previous page. This allows you to fetch the "next" page. If the ORDER BY
direction is ASC
, simply use >
instead.
With the above method, you cannot immediately jump to page 4 without having first fetched the previous 40 records. But often, you do not want to jump that far anyway. Instead, you get a much faster query that might be able to fetch data in constant time, depending on your indexing. Plus, your pages remain "stable", no matter if the underlying data changes (e.g. on page 1, while you're on page 4).
This is the best way to implement pagination when lazy loading more data in web applications, for instance.
Note, the "seek method" is also called keyset pagination.
Total records before pagination
The COUNT(*) OVER()
window function will help you count the number of total records "before pagination". If you're using SQL Server 2000, you will have to resort to two queries for the COUNT(*)
.

- 1
- 1

- 211,314
- 129
- 689
- 1,509
-
1
-
3@user960567: In terms of performance, keyset paging will always beat offset paging, no matter whether you implement offset paging with the SQL standard `OFFSET .. FETCH`, or with previous `ROW_NUMBER()` tricks. – Lukas Eder Apr 16 '14 at 14:31
-
1Lukas, from my testing `WITH C AS(SELECT TOP(@rowsPerPage * @pageNum), ResultNum = ROW_NUMBER() OVER (ORDER BY id)...) SELECT * FROM C WHERE ResultNum > ((@pageNum - 1) * @rowsPerPage)` was fastest. http://sqlserverplanet.com/optimization/best-sql-server-pagination-method – Imran Qadir Baksh - Baloch Apr 16 '14 at 15:55
-
For what value of `@pageNum`? Be sure to read [this whole article](http://use-the-index-luke.com/sql/partial-results/fetch-next-page). You'll see that offset paging won't beat keyset paging, even if it can *appear* fast for low offsets. – Lukas Eder Apr 16 '14 at 16:12
-
-
I have tried first which was taking upto 3 seconds. The `WITH C AS` takes only 1 ms. – Imran Qadir Baksh - Baloch Apr 16 '14 at 16:17
-
Well, you do have appropriate indexes set in place for keyset paging, right? :-) – Lukas Eder Apr 16 '14 at 16:27
-
-
31I have three issues with the seek method. [1] A user can't jump to page. [2] it assumes sequential keys i.e. if someone deletes some 3 rows, then I get a page of 7 items instead of 10. `RowNumber` gives me a consistent 10 items per page. [3] it doesn't work with existing grids that assume `pagenumber` and `pagesize`. – Rebecca Jul 29 '15 at 09:55
-
@Junto: [1] correct, but this isn't always needed. You can still "emulate" jumping to the next few pages by making the actual page number bigger than the one displayed to the user. [2] That shouldn't happen, perhaps you don't have a unique sort criteria? [3] That's correct. But related to the OP's question, that wasn't really a requirement.. – Lukas Eder Jul 29 '15 at 09:58
-
9@Junto: keyset paging isn't appropriate for all cases. It's definitely not for data grids. But it's perfect for scenarios like infinite scrolling of Facebook feed page. Doesn't matter if new posts are being added at the top, your subsequent feed posts will be correctly added to the bottom while you're scrolling down. Perfect usage example for this... Such thing would be much **much** harder to implement using offset limit/fetch using numbers only. – Robert Koritnik Jul 31 '15 at 00:21
-
5I have to agree with Junto. This method completely rules out a client that has a pretty standard pagination ui of "Previous 1 2 3 (4) 5 6 Next" where users can jump ahead. This is not exactly an edge case in my experience... – AaronHS Nov 27 '15 at 06:10
-
-
3Keyset pagination article [here](http://use-the-index-luke.com/no-offset) – Stphane Aug 09 '17 at 16:30
-
@Stphane It's not wrong, but IMHO quite useless article (have read it before). The trouble is, that it doesn't explain, how to do a useful, real-world queries (i.e. with filtering entries). It's doable with keyset approach, but painful, like in one of the answer above. Speed is falliing to. Not exactly a win-win situation, as it often seems to be presented. – Oak_3260548 Jul 30 '20 at 09:13
-
@Oak_3260548: [Use jOOQ](https://www.jooq.org/doc/latest/manual/sql-building/sql-statements/select-statement/seek-clause/) to get access to a simple syntax for no-pain keyset paginatino. – Lukas Eder Aug 04 '20 at 12:46
-
From SQL Server 2012, we can use OFFSET
and FETCH NEXT
Clause to achieve the pagination.
Try this, for SQL Server:
In the SQL Server 2012 a new feature was added in the ORDER BY clause, to query optimization of a set data, making work easier with data paging for anyone who writes in T-SQL as well for the entire Execution Plan in SQL Server.
Below the T-SQL script with the same logic used in the previous example.
--CREATING A PAGING WITH OFFSET and FETCH clauses IN "SQL SERVER 2012" DECLARE @PageNumber AS INT, @RowspPage AS INT SET @PageNumber = 2 SET @RowspPage = 10 SELECT ID_EXAMPLE, NM_EXAMPLE, DT_CREATE FROM TB_EXAMPLE ORDER BY ID_EXAMPLE OFFSET ((@PageNumber - 1) * @RowspPage) ROWS FETCH NEXT @RowspPage ROWS ONLY;
-
2
-
2@Vikrant only if you if you ignore all the people running lower version than 2012 – The Fool Aug 19 '20 at 08:42
-
2The question also asks for total number of rows before paging, which this answer does not answer. – Kissaki Sep 23 '21 at 08:52
MSDN: ROW_NUMBER (Transact-SQL)
Returns the sequential number of a row within a partition of a result set, starting at 1 for the first row in each partition.
The following example returns rows with numbers 50 to 60 inclusive in the order of the OrderDate.
WITH OrderedOrders AS
(
SELECT
ROW_NUMBER() OVER(ORDER BY FirstName DESC) AS RowNumber,
FirstName, LastName, ROUND(SalesYTD,2,1) AS "Sales YTD"
FROM [dbo].[vSalesPerson]
)
SELECT RowNumber,
FirstName, LastName, Sales YTD
FROM OrderedOrders
WHERE RowNumber > 50 AND RowNumber < 60;
RowNumber FirstName LastName SalesYTD
--- ----------- ---------------------- -----------------
1 Linda Mitchell 4251368.54
2 Jae Pak 4116871.22
3 Michael Blythe 3763178.17
4 Jillian Carson 3189418.36
5 Ranjit Varkey Chudukatil 3121616.32
6 José Saraiva 2604540.71
7 Shu Ito 2458535.61
8 Tsvi Reiter 2315185.61
9 Rachel Valdez 1827066.71
10 Tete Mensa-Annan 1576562.19
11 David Campbell 1573012.93
12 Garrett Vargas 1453719.46
13 Lynn Tsoflias 1421810.92
14 Pamela Ansman-Wolfe 1352577.13

- 5,276
- 3
- 30
- 49

- 1,119
- 12
- 13
-
The question also asks for total number of rows before paging, which this answer does not answer. – Kissaki Sep 23 '21 at 08:53
There is a good overview of different paging techniques at http://www.codeproject.com/KB/aspnet/PagingLarge.aspx
I've used ROWCOUNT method quite often mostly with SQL Server 2000 (will work with 2005 & 2008 too, just measure performance compared to ROW_NUMBER), it's lightning fast, but you need to make sure that the sorted column(s) have (mostly) unique values.

- 8,100
- 17
- 57
- 96

- 11,260
- 2
- 29
- 29
-
2Interestingly, that article doesn't mention the [seek method](http://stackoverflow.com/a/19609938/521799), which is able to perform paging in constant time... Still a good article – Lukas Eder Oct 26 '13 at 17:53
For SQL Server 2000 you can simulate ROW_NUMBER() using a table variable with an IDENTITY column:
DECLARE @pageNo int -- 1 based
DECLARE @pageSize int
SET @pageNo = 51
SET @pageSize = 20
DECLARE @firstRecord int
DECLARE @lastRecord int
SET @firstRecord = (@pageNo - 1) * @pageSize + 1 -- 1001
SET @lastRecord = @firstRecord + @pageSize - 1 -- 1020
DECLARE @orderedKeys TABLE (
rownum int IDENTITY NOT NULL PRIMARY KEY CLUSTERED,
TableKey int NOT NULL
)
SET ROWCOUNT @lastRecord
INSERT INTO @orderedKeys (TableKey) SELECT ID FROM Orders WHERE OrderDate >= '1980-01-01' ORDER BY OrderDate
SET ROWCOUNT 0
SELECT t.*
FROM Orders t
INNER JOIN @orderedKeys o ON o.TableKey = t.ID
WHERE o.rownum >= @firstRecord
ORDER BY o.rownum
This approach can be extended to tables with multi-column keys, and it doesn't incur the performance overhead of using OR (which skips index usage). The downside is the amount of temporary space used up if the data set is very large and one is near the last page. I did not test cursor performance in that case, but it might be better.
Note that this approach could be optimized for the first page of data. Also, ROWCOUNT was used since TOP does not accept a variable in SQL Server 2000.

- 1,294
- 9
- 10
Use case wise the following seem to be easy to use and fast. Just set the page number.
use AdventureWorks
DECLARE @RowsPerPage INT = 10, @PageNumber INT = 6;
with result as(
SELECT SalesOrderDetailID, SalesOrderID, ProductID,
ROW_NUMBER() OVER (ORDER BY SalesOrderDetailID) AS RowNum
FROM Sales.SalesOrderDetail
where 1=1
)
select SalesOrderDetailID, SalesOrderID, ProductID from result
WHERE result.RowNum BETWEEN ((@PageNumber-1)*@RowsPerPage)+1
AND @RowsPerPage*(@PageNumber)
also without CTE
use AdventureWorks
DECLARE @RowsPerPage INT = 10, @PageNumber INT = 6
SELECT SalesOrderDetailID, SalesOrderID, ProductID
FROM (
SELECT SalesOrderDetailID, SalesOrderID, ProductID,
ROW_NUMBER() OVER (ORDER BY SalesOrderDetailID) AS RowNum
FROM Sales.SalesOrderDetail
where 1=1
) AS SOD
WHERE SOD.RowNum BETWEEN ((@PageNumber-1)*@RowsPerPage)+1
AND @RowsPerPage*(@PageNumber)

- 1,456
- 1
- 12
- 21

- 10,366
- 25
- 84
- 114
-
1
-
It's a placeholder for where clause. You can easily add more clauses with `AND` on new lines and comment or remove them if needed. – kost Jun 11 '21 at 00:25
The best way for paging in sql server 2012 is by using offset and fetch next in a stored procedure. OFFSET Keyword - If we use offset with the order by clause then the query will skip the number of records we specified in OFFSET n Rows.
FETCH NEXT Keywords - When we use Fetch Next with an order by clause only it will returns the no of rows you want to display in paging, without Offset then SQL will generate an error. here is the example given below.
create procedure sp_paging
(
@pageno as int,
@records as int
)
as
begin
declare @offsetcount as int
set @offsetcount=(@pageno-1)*@records
select id,bs,variable from salary order by id offset @offsetcount rows fetch Next @records rows only
end
you can execute it as follow.
exec sp_paging 2,3

- 5,334
- 46
- 38
These are my solutions for paging the result of query in SQL server side. these approaches are different between SQL Server 2008 and 2012. Also, I have added the concept of filtering and order by with one column. It is very efficient when you are paging and filtering and ordering in your Gridview.
Before testing, you have to create one sample table and insert some row in this table : (In real world you have to change Where clause considering your table fields and maybe you have some join and subquery in main part of select)
Create Table VLT
(
ID int IDentity(1,1),
Name nvarchar(50),
Tel Varchar(20)
)
GO
Insert INTO VLT
VALUES
('NAME' + Convert(varchar(10),@@identity),'FAMIL' + Convert(varchar(10),@@identity))
GO 500000
In all of these sample, I want to query 200 rows per page and I am fetching the row for page number 1200.
In SQL server 2008, you can use the CTE concept. Because of that, I have written two type of query for SQL server 2008+
-- SQL Server 2008+
DECLARE @PageNumber Int = 1200
DECLARE @PageSize INT = 200
DECLARE @SortByField int = 1 --The field used for sort by
DECLARE @SortOrder nvarchar(255) = 'ASC' --ASC or DESC
DECLARE @FilterType nvarchar(255) = 'None' --The filter type, as defined on the client side (None/Contain/NotContain/Match/NotMatch/True/False/)
DECLARE @FilterValue nvarchar(255) = '' --The value the user gave for the filter
DECLARE @FilterColumn int = 1 --The column to wich the filter is applied, represents the column number like when we send the information.
SELECT
Data.ID,
Data.Name,
Data.Tel
FROM
(
SELECT
ROW_NUMBER()
OVER( ORDER BY
CASE WHEN @SortByField = 1 AND @SortOrder = 'ASC'
THEN VLT.ID END ASC,
CASE WHEN @SortByField = 1 AND @SortOrder = 'DESC'
THEN VLT.ID END DESC,
CASE WHEN @SortByField = 2 AND @SortOrder = 'ASC'
THEN VLT.Name END ASC,
CASE WHEN @SortByField = 2 AND @SortOrder = 'DESC'
THEN VLT.Name END ASC,
CASE WHEN @SortByField = 3 AND @SortOrder = 'ASC'
THEN VLT.Tel END ASC,
CASE WHEN @SortByField = 3 AND @SortOrder = 'DESC'
THEN VLT.Tel END ASC
) AS RowNum
,*
FROM VLT
WHERE
( -- We apply the filter logic here
CASE
WHEN @FilterType = 'None' THEN 1
-- Name column filter
WHEN @FilterType = 'Contain' AND @FilterColumn = 1
AND ( -- In this case, when the filter value is empty, we want to show everything.
VLT.ID LIKE '%' + @FilterValue + '%'
OR
@FilterValue = ''
) THEN 1
WHEN @FilterType = 'NotContain' AND @FilterColumn = 1
AND ( -- In this case, when the filter value is empty, we want to show everything.
VLT.ID NOT LIKE '%' + @FilterValue + '%'
OR
@FilterValue = ''
) THEN 1
WHEN @FilterType = 'Match' AND @FilterColumn = 1
AND VLT.ID = @FilterValue THEN 1
WHEN @FilterType = 'NotMatch' AND @FilterColumn = 1
AND VLT.ID <> @FilterValue THEN 1
-- Name column filter
WHEN @FilterType = 'Contain' AND @FilterColumn = 2
AND ( -- In this case, when the filter value is empty, we want to show everything.
VLT.Name LIKE '%' + @FilterValue + '%'
OR
@FilterValue = ''
) THEN 1
WHEN @FilterType = 'NotContain' AND @FilterColumn = 2
AND ( -- In this case, when the filter value is empty, we want to show everything.
VLT.Name NOT LIKE '%' + @FilterValue + '%'
OR
@FilterValue = ''
) THEN 1
WHEN @FilterType = 'Match' AND @FilterColumn = 2
AND VLT.Name = @FilterValue THEN 1
WHEN @FilterType = 'NotMatch' AND @FilterColumn = 2
AND VLT.Name <> @FilterValue THEN 1
-- Tel column filter
WHEN @FilterType = 'Contain' AND @FilterColumn = 3
AND ( -- In this case, when the filter value is empty, we want to show everything.
VLT.Tel LIKE '%' + @FilterValue + '%'
OR
@FilterValue = ''
) THEN 1
WHEN @FilterType = 'NotContain' AND @FilterColumn = 3
AND ( -- In this case, when the filter value is empty, we want to show everything.
VLT.Tel NOT LIKE '%' + @FilterValue + '%'
OR
@FilterValue = ''
) THEN 1
WHEN @FilterType = 'Match' AND @FilterColumn = 3
AND VLT.Tel = @FilterValue THEN 1
WHEN @FilterType = 'NotMatch' AND @FilterColumn = 3
AND VLT.Tel <> @FilterValue THEN 1
END
) = 1
) AS Data
WHERE Data.RowNum > @PageSize * (@PageNumber - 1)
AND Data.RowNum <= @PageSize * @PageNumber
ORDER BY Data.RowNum
GO
And second solution with CTE in SQL server 2008+
DECLARE @PageNumber Int = 1200
DECLARE @PageSize INT = 200
DECLARE @SortByField int = 1 --The field used for sort by
DECLARE @SortOrder nvarchar(255) = 'ASC' --ASC or DESC
DECLARE @FilterType nvarchar(255) = 'None' --The filter type, as defined on the client side (None/Contain/NotContain/Match/NotMatch/True/False/)
DECLARE @FilterValue nvarchar(255) = '' --The value the user gave for the filter
DECLARE @FilterColumn int = 1 --The column to wich the filter is applied, represents the column number like when we send the information.
;WITH
Data_CTE
AS
(
SELECT
ROW_NUMBER()
OVER( ORDER BY
CASE WHEN @SortByField = 1 AND @SortOrder = 'ASC'
THEN VLT.ID END ASC,
CASE WHEN @SortByField = 1 AND @SortOrder = 'DESC'
THEN VLT.ID END DESC,
CASE WHEN @SortByField = 2 AND @SortOrder = 'ASC'
THEN VLT.Name END ASC,
CASE WHEN @SortByField = 2 AND @SortOrder = 'DESC'
THEN VLT.Name END ASC,
CASE WHEN @SortByField = 3 AND @SortOrder = 'ASC'
THEN VLT.Tel END ASC,
CASE WHEN @SortByField = 3 AND @SortOrder = 'DESC'
THEN VLT.Tel END ASC
) AS RowNum
,*
FROM VLT
WHERE
( -- We apply the filter logic here
CASE
WHEN @FilterType = 'None' THEN 1
-- Name column filter
WHEN @FilterType = 'Contain' AND @FilterColumn = 1
AND ( -- In this case, when the filter value is empty, we want to show everything.
VLT.ID LIKE '%' + @FilterValue + '%'
OR
@FilterValue = ''
) THEN 1
WHEN @FilterType = 'NotContain' AND @FilterColumn = 1
AND ( -- In this case, when the filter value is empty, we want to show everything.
VLT.ID NOT LIKE '%' + @FilterValue + '%'
OR
@FilterValue = ''
) THEN 1
WHEN @FilterType = 'Match' AND @FilterColumn = 1
AND VLT.ID = @FilterValue THEN 1
WHEN @FilterType = 'NotMatch' AND @FilterColumn = 1
AND VLT.ID <> @FilterValue THEN 1
-- Name column filter
WHEN @FilterType = 'Contain' AND @FilterColumn = 2
AND ( -- In this case, when the filter value is empty, we want to show everything.
VLT.Name LIKE '%' + @FilterValue + '%'
OR
@FilterValue = ''
) THEN 1
WHEN @FilterType = 'NotContain' AND @FilterColumn = 2
AND ( -- In this case, when the filter value is empty, we want to show everything.
VLT.Name NOT LIKE '%' + @FilterValue + '%'
OR
@FilterValue = ''
) THEN 1
WHEN @FilterType = 'Match' AND @FilterColumn = 2
AND VLT.Name = @FilterValue THEN 1
WHEN @FilterType = 'NotMatch' AND @FilterColumn = 2
AND VLT.Name <> @FilterValue THEN 1
-- Tel column filter
WHEN @FilterType = 'Contain' AND @FilterColumn = 3
AND ( -- In this case, when the filter value is empty, we want to show everything.
VLT.Tel LIKE '%' + @FilterValue + '%'
OR
@FilterValue = ''
) THEN 1
WHEN @FilterType = 'NotContain' AND @FilterColumn = 3
AND ( -- In this case, when the filter value is empty, we want to show everything.
VLT.Tel NOT LIKE '%' + @FilterValue + '%'
OR
@FilterValue = ''
) THEN 1
WHEN @FilterType = 'Match' AND @FilterColumn = 3
AND VLT.Tel = @FilterValue THEN 1
WHEN @FilterType = 'NotMatch' AND @FilterColumn = 3
AND VLT.Tel <> @FilterValue THEN 1
END
) = 1
)
SELECT
Data.ID,
Data.Name,
Data.Tel
FROM Data_CTE AS Data
WHERE Data.RowNum > @PageSize * (@PageNumber - 1)
AND Data.RowNum <= @PageSize * @PageNumber
ORDER BY Data.RowNum
-- SQL Server 2012+
DECLARE @PageNumber Int = 1200
DECLARE @PageSize INT = 200
DECLARE @SortByField int = 1 --The field used for sort by
DECLARE @SortOrder nvarchar(255) = 'ASC' --ASC or DESC
DECLARE @FilterType nvarchar(255) = 'None' --The filter type, as defined on the client side (None/Contain/NotContain/Match/NotMatch/True/False/)
DECLARE @FilterValue nvarchar(255) = '' --The value the user gave for the filter
DECLARE @FilterColumn int = 1 --The column to wich the filter is applied, represents the column number like when we send the information.
;WITH
Data_CTE
AS
(
SELECT
*
FROM VLT
WHERE
( -- We apply the filter logic here
CASE
WHEN @FilterType = 'None' THEN 1
-- Name column filter
WHEN @FilterType = 'Contain' AND @FilterColumn = 1
AND ( -- In this case, when the filter value is empty, we want to show everything.
VLT.ID LIKE '%' + @FilterValue + '%'
OR
@FilterValue = ''
) THEN 1
WHEN @FilterType = 'NotContain' AND @FilterColumn = 1
AND ( -- In this case, when the filter value is empty, we want to show everything.
VLT.ID NOT LIKE '%' + @FilterValue + '%'
OR
@FilterValue = ''
) THEN 1
WHEN @FilterType = 'Match' AND @FilterColumn = 1
AND VLT.ID = @FilterValue THEN 1
WHEN @FilterType = 'NotMatch' AND @FilterColumn = 1
AND VLT.ID <> @FilterValue THEN 1
-- Name column filter
WHEN @FilterType = 'Contain' AND @FilterColumn = 2
AND ( -- In this case, when the filter value is empty, we want to show everything.
VLT.Name LIKE '%' + @FilterValue + '%'
OR
@FilterValue = ''
) THEN 1
WHEN @FilterType = 'NotContain' AND @FilterColumn = 2
AND ( -- In this case, when the filter value is empty, we want to show everything.
VLT.Name NOT LIKE '%' + @FilterValue + '%'
OR
@FilterValue = ''
) THEN 1
WHEN @FilterType = 'Match' AND @FilterColumn = 2
AND VLT.Name = @FilterValue THEN 1
WHEN @FilterType = 'NotMatch' AND @FilterColumn = 2
AND VLT.Name <> @FilterValue THEN 1
-- Tel column filter
WHEN @FilterType = 'Contain' AND @FilterColumn = 3
AND ( -- In this case, when the filter value is empty, we want to show everything.
VLT.Tel LIKE '%' + @FilterValue + '%'
OR
@FilterValue = ''
) THEN 1
WHEN @FilterType = 'NotContain' AND @FilterColumn = 3
AND ( -- In this case, when the filter value is empty, we want to show everything.
VLT.Tel NOT LIKE '%' + @FilterValue + '%'
OR
@FilterValue = ''
) THEN 1
WHEN @FilterType = 'Match' AND @FilterColumn = 3
AND VLT.Tel = @FilterValue THEN 1
WHEN @FilterType = 'NotMatch' AND @FilterColumn = 3
AND VLT.Tel <> @FilterValue THEN 1
END
) = 1
)
SELECT
Data.ID,
Data.Name,
Data.Tel
FROM Data_CTE AS Data
ORDER BY
CASE WHEN @SortByField = 1 AND @SortOrder = 'ASC'
THEN Data.ID END ASC,
CASE WHEN @SortByField = 1 AND @SortOrder = 'DESC'
THEN Data.ID END DESC,
CASE WHEN @SortByField = 2 AND @SortOrder = 'ASC'
THEN Data.Name END ASC,
CASE WHEN @SortByField = 2 AND @SortOrder = 'DESC'
THEN Data.Name END ASC,
CASE WHEN @SortByField = 3 AND @SortOrder = 'ASC'
THEN Data.Tel END ASC,
CASE WHEN @SortByField = 3 AND @SortOrder = 'DESC'
THEN Data.Tel END ASC
OFFSET @PageSize * (@PageNumber - 1) ROWS FETCH NEXT @PageSize ROWS ONLY;

- 11,967
- 21
- 108
- 144
Try this approach:
SELECT TOP @offset a.*
FROM (select top @limit b.*, COUNT(*) OVER() totalrows
from TABLENAME b order by id asc) a
ORDER BY id desc;

- 3,880
- 4
- 42
- 65

- 19
- 1
CREATE view vw_sppb_part_listsource as
select row_number() over (partition by sppb_part.init_id order by sppb_part.sppb_part_id asc ) as idx, * from (
select
part.SPPB_PART_ID
, 0 as is_rev
, part.part_number
, part.init_id
from t_sppb_init_part part
left join t_sppb_init_partrev prev on ( part.SPPB_PART_ID = prev.SPPB_PART_ID )
where prev.SPPB_PART_ID is null
union
select
part.SPPB_PART_ID
, 1 as is_rev
, prev.part_number
, part.init_id
from t_sppb_init_part part
inner join t_sppb_init_partrev prev on ( part.SPPB_PART_ID = prev.SPPB_PART_ID )
) sppb_part
will restart idx when it comes to different init_id

- 11
- 1
Well I have used the following sample query in my SQL 2000 database, it works well for SQL 2005 too. The power it gives you is dynamically order by using multiple columns. I tell you ... this is powerful :)
ALTER PROCEDURE [dbo].[RE_ListingReports_SelectSummary]
@CompanyID int,
@pageNumber int,
@pageSize int,
@sort varchar(200)
AS
DECLARE @sql nvarchar(4000)
DECLARE @strPageSize nvarchar(20)
DECLARE @strSkippedRows nvarchar(20)
DECLARE @strFields nvarchar(4000)
DECLARE @strFilter nvarchar(4000)
DECLARE @sortBy nvarchar(4000)
DECLARE @strFrom nvarchar(4000)
DECLARE @strID nvarchar(100)
If(@pageNumber < 0)
SET @pageNumber = 1
SET @strPageSize = CAST(@pageSize AS varchar(20))
SET @strSkippedRows = CAST(((@pageNumber - 1) * @pageSize) AS varchar(20))-- For example if pageNumber is 5 pageSize is 10, then SkippedRows = 40.
SET @strID = 'ListingDbID'
SET @strFields = 'ListingDbID,
ListingID,
[ExtraRoom]
'
SET @strFrom = ' vwListingSummary '
SET @strFilter = ' WHERE
CompanyID = ' + CAST(@CompanyID As varchar(20))
End
SET @sortBy = ''
if(len(ltrim(rtrim(@sort))) > 0)
SET @sortBy = ' Order By ' + @sort
-- Total Rows Count
SET @sql = 'SELECT Count(' + @strID + ') FROM ' + @strFROM + @strFilter
EXEC sp_executesql @sql
--// This technique is used in a Single Table pagination
SET @sql = 'SELECT ' + @strFields + ' FROM ' + @strFROM +
' WHERE ' + @strID + ' IN ' +
' (SELECT TOP ' + @strPageSize + ' ' + @strID + ' FROM ' + @strFROM + @strFilter +
' AND ' + @strID + ' NOT IN ' + '
(SELECT TOP ' + @strSkippedRows + ' ' + @strID + ' FROM ' + @strFROM + @strFilter + @SortBy + ') '
+ @SortBy + ') ' + @SortBy
Print @sql
EXEC sp_executesql @sql
The best part is sp_executesql caches later calls, provided you pass same parameters i.e generate same sql text.

- 14,502
- 8
- 45
- 51
For the ROW_NUMBER
technique, if you do not have a sorting column to use, you can use the CURRENT_TIMESTAMP
as follows:
SELECT TOP 20
col1,
col2,
col3,
col4
FROM (
SELECT
tbl.col1 AS col1
,tbl.col2 AS col2
,tbl.col3 AS col3
,tbl.col4 AS col4
,ROW_NUMBER() OVER (
ORDER BY CURRENT_TIMESTAMP
) AS sort_row
FROM dbo.MyTable tbl
) AS query
WHERE query.sort_row > 10
ORDER BY query.sort_row
This has worked well for me for searches over table sizes of even up to 700,000.
This fetches records 11 to 30.

- 7,751
- 11
- 54
- 79
-
As a good practice, with pagination you should try to order by a unique set of columns in the result set since order should not be thought of as guaranteed. – Arin Taylor Sep 25 '16 at 21:55
-
2
create PROCEDURE SP_Company_List (@pagesize int = -1 ,@pageindex int= 0 ) > AS BEGIN SET NOCOUNT ON; select Id , NameEn from Company ORDER by Id ASC OFFSET (@pageindex-1 )* @pagesize ROWS FETCH NEXt @pagesize ROWS ONLY END GO
DECLARE @return_value int EXEC @return_value = [dbo].[SP_Company_List] @pagesize = 1 , > @pageindex = 2 SELECT 'Return Value' = @return_value GO

- 57
- 4
This bit gives you ability to paginate using SQL Server, and newer versions of MySQL and carries the total number of rows in every row. Uses your pimary key to count number of unique rows.
WITH T AS
(
SELECT TABLE_ID, ROW_NUMBER() OVER (ORDER BY TABLE_ID) AS RN
, (SELECT COUNT(TABLE_ID) FROM TABLE) AS TOTAL
FROM TABLE (NOLOCK)
)
SELECT T2.FIELD1, T2.FIELD2, T2.FIELD3, T.TOTAL
FROM TABLE T2 (NOLOCK)
INNER JOIN T ON T2.TABLE_ID=T.TABLE_ID
WHERE T.RN >= 100
AND T.RN < 200

- 141
- 2
- 6
You didn't specify the language nor which driver you are using. Therefore I'm describing it abstractly.
- Create a scrollable resultset / dataset. This required a primary on the table(s)
- jump to the end
- request the row count
- jump to the start of the page
- scroll through the rows until the end of the page

- 330,807
- 53
- 334
- 373

- 23,758
- 21
- 98
- 156