Dynamic order by without using dynamic sql? - sql

I have the following stored procedure which can be sorted ascending and descending on TemplateName,CreatedOn and UploadedBy. The following SP when runs doesnot sort records.if i replace 2,3,4 by columnname, i got an error message "Conversion failed when converting the nvarchar value 'Test Template' to data type int.".Please suggest how to achieve sorting.
CREATE PROCEDURE [dbo].[usp_SEL_GetRenderingTemplate]
(
#facilityID INT,
#sortOrder VARCHAR(5),
#sortExpression VARCHAR(100),
#errorCode INT OUTPUT
)
AS
BEGIN
SET NOCOUNT ON ;
BEGIN TRY
SET #sortOrder = CASE #sortOrder
WHEN 'Ascending' THEN 'ASC'
WHEN 'Descending' THEN 'DESC'
ELSE 'ASC'
END
SELECT TemplateID,
TemplateName,
CreatedOn,
( [user].LastName + ' ' + [user].FirstName ) AS UploadedBy
FROM Templates
INNER JOIN [user] ON [user].UserID = Templates.CreatedBy
WHERE facilityid = #facilityID
ORDER BY CASE WHEN #sortExpression = 'TemplateName'
AND #sortOrder = 'ASC' THEN 2
WHEN #sortExpression = 'CreatedOn'
AND #sortOrder = 'ASC' THEN 3
WHEN #sortExpression = 'UploadedBy'
AND #sortOrder = 'ASC' THEN 4
END ASC,
CASE WHEN #sortExpression = 'TemplateName'
AND #sortOrder = 'DESC' THEN 2
WHEN #sortExpression = 'CreatedOn'
AND #sortOrder = 'DESC' THEN 3
WHEN #sortExpression = 'UploadedBy'
AND #sortOrder = 'DESC' THEN 4
END DESC
SET #errorCode = 0
END TRY
BEGIN CATCH
SET #errorCode = -1
DECLARE #errorMsg AS VARCHAR(MAX)
DECLARE #utcDate AS DATETIME
SET #errorMsg = CAST(ERROR_MESSAGE() AS VARCHAR(MAX))
SET #utcDate = CAST(GETUTCDATE() AS DATETIME)
EXEC usp_INS_LogException 'usp_SEL_GetFacilityWorkTypeList',
#errorMsg, #utcDate
END CATCH
END

The dynamic ordering has to be of the same datatype. Here is an example of something that I use to case order three different datatypes - integer, date (ascending and descending), and string. This may not work for your situation, but at least you can see some techniques for casting to a common datatype.
...
ORDER BY
Case Parent.RankTypeID
When 0 Then dbo.Documents.Rank
When 1 Then Convert(int, dbo.Documents.DateStart, 112)
When 2 Then (1 - Convert(int, dbo.Documents.DateStart, 112))
When 3 Then Cast(dbo.Documents.Title as sql_variant)
End
Note:
112 is a date format of YYYYMMDD - useful for ordering.

We perform a similar sort of dynamic ordering in one of our products.
The only real different to your code is firstly, we don't use the live join.
We create a temporary table, so we can perform paging, and then apply the order.
We also use ints for ordering, cuts down on the overhead of string comparison.
It does make your SQL a bit longer, but this approach is definitely faster for the Query Optimiser. Also, and more importantly, I don't think you can mix types in Switch blocks, for ordering, so to follow your original code, you'd have to CONVERT all your data to the same time, which defeats the object :(
So you'd have
DECLARE #temp TABLE(ID int identity(1,1), TemplateID int, TemplateName nvarchar(100), CreatedOn datetime, UploadedBy nvarchar(100))
INSERT INTO #temp(TemplateID, TemplateName, CreatedOn, UploadedBy)
SELECT TemplateID,
TemplateName,
CreatedOn,
( [user].LastName + ' ' + [user].FirstName ) AS UploadedBy
FROM Templates
INNER JOIN [user] ON [user].UserID = Templates.CreatedBy
WHERE facilityid = #facilityID
Then:
IF #SortOrder = 1 --'ASC'
BEGIN
IF #sort = 2
Select *
From #Temp
Order by TemplateName ASC
ELSE IF #sort = 3
Select *
From #Temp
Order By CreatedBy ASC
-- and so on...
END
ELSE -- descending
BEGIN
-- Ad. Inf.
END
Delete
From #Temp
WHERE ID < #pageStart or ID > #pageStart + #pageSize

The following SP when runs doesnot
sort records.if i replace 2,3,4 by
columnname,
You shouldn't replace 2, 3, 4 in the ORDER BY clause by the column name. When you call the procedure just pass the column you want to sort by as the 3rd parameter.
EXEC [dbo].[usp_SEL_GetRenderingTemplate] 1,'Ascending','CreatedOn',#vErrorCode
The CASE will then evaluate the query to something like
...ORDER BY 3 DESC
which sorts the query by the 3rd column in the SELECT clause (i.e. CreatedOn)

The problem is that in CASE clauses the return values must have the same data type.
You can work around this problem by using multiple CASE statements http://www.extremeexperts.com/sql/articles/CASEinORDER.aspx
Or casting everything to the same data type.

Related

Optimizing a dynamic sorting SQL query

This query (I've simulated it to show you. Otherwise, it is longer with many more lists of columns, some more tables joined; and finally to sort by every column as required by the user) takes too long, and can timeout from the UI sometimes. I was trying to optimize the stored procedure to reduce loading time without affecting the retrieved lists. I was considering to select * from the temporary table and then Order from there. That could help somehow. I also considered using a dynamic sql without specifying the #sortidr (DESC/ASC), but the user should have the flexibility to order either way.
I was wondering if anyone can suggest better ways of optimizing it.
CREATE PROCEDURE get_lists
#employeeid int
#ColumnName varchar(100) = NULL,
#sortidr varchar(4) = NULL,
#start_date datetime = NULL
AS
CREATE #tempemp
(emp_id int, first_name varchar(20), last_name varchar(20), SSN int)
INSERT INTO #tempemp
SELECT team_action, rec_id, total_records, cfirst_name, efirst_name, has_desc, category_name
FROM
(SELECT COALESCE(lt.description, lt.action) AS team_action
,cl.rec_id
,total_records = count(*) Over()
,c.cfirst_name
,e.efirst_name
, has_desc = cld.changelog_id IS NOT NULL THEN 1 ELSE 0 END
, ct.category_name
FROM claimll cll
JOIN itemtype lt ON cll.clLog_id = lt.clLog_id
LEFT JOIN categgory ct ON ct.id=clc.cat_id
LEFT JOIN clients c ON ct.client_id = c.client_id
LEFT JOIN employees e ON cll.emp_id = e.emp_id
LEFT JOIN detail d ON d.change_id = cll.change_id
WHERE cll.change_date > #start_date
AND cll.emp_id = #employeeid OR cll.by_employeeid = #employeeid
) t
ORDER BY
CASE WHEN #sortidr = 'asc' AND #ColumnName='team_action' THEN team_action END,
CASE WHEN #sortidr = 'desc' AND #ColumnName = 'team_action' THEN team_action END DESC,
CASE WHEN #sortidr = 'asc' AND #ColumnName='rec_id' THEN rec_id END,
CASE WHEN #sortidr = 'desc' AND #ColumnName = 'rec_id' THEN rec_id END DESC,
CASE WHEN #sortidr = 'asc' AND #ColumnName='total_records' THEN total_records END,
CASE WHEN #sortidr = 'desc' AND #ColumnName = 'total_records' THEN total_records END DESC,
CASE WHEN #sortidr = 'asc' AND #ColumnName='cfirst_name' THEN cfirst_name END,
CASE WHEN #sortidr = 'desc' AND #ColumnName = 'cfirst_name' THEN cfirst_name END DESC,
CASE WHEN #ColumnName IS NULL THEN first_name END DESC
I'm not at a PC so I won't be able to add code until later but you have provided enough information for me to provide some good insight.
First - the optimizer will never be able to leverage an index to prevent a sort when you order by a CASE statement. That said, there are many reasons your query could be slow; you should remove that logic then test performance to understand if that's the culprit.
Based on the limited info I have my suggestion would be update your proc to:
1. Use dynamic SQL to build your temp table, as you have done, but without the CASE statement.
Included in this ^^^ dynamic SQL should be code to build a clustered index with #columnName as the cluster key and using #sortDir to dictate the index sort order.
Now you have a temp table correctly indexed based on user supplied parameters.
Add a second dynamic SQL statement that does a SELECT * FROM #temp... You will again use #columnName and #sortOrder to build a SARGable ORDER BY (one which will leverage the index from Step #1.
Test with different parameters always looking at the Actual Execution plan to further tune your indexes.
Hope this helps. I'll add demo code a little later if you need.
Updated On 2/22/2019 to include examples:
Here's a stored proc that does what I'm describing (note the comments)--
SET NOCOUNT ON;
USE tempdb
GO
IF OBJECT_ID('dbo.DynamicSortProc','P') IS NOT NULL DROP PROC dbo.DynamicSortProc;
GO
CREATE PROC dbo.DynamicSortProc #sortby NVARCHAR(100), #sortorder NVARCHAR(4)
AS
BEGIN
-- 1. Populate the temp table (sample data)
IF OBJECT_ID('##tmp','U') IS NOT NULL DROP TABLE ##tmp;
SELECT SomeId = IDENTITY(INT,1,1), ColA, ColB
INTO ##tmp
FROM (VALUES(1,3),(6,5),(2,8),(5,5),(2,6),(5,1)) AS f(ColA,ColB);
-- NOTE: On SQL 2014+ SELECT INTO is often faster than the standard create+insert
;
-- 2. Setup #sortby
SET #sortby = QUOTENAME(#sortby)+' '+CASE #sortorder WHEN 'DESC' THEN 'DESC' ELSE '' END;
-- 3. Set up the Dynamic SQL
DECLARE #createIndex NVARCHAR(4000) = 'CREATE CLUSTERED INDEX x ON ##tmp ('+#sortby+') ';
DECLARE #executeQuery NVARCHAR(4000) = 'SELECT t.* FROM ##tmp AS t ORDER BY '+#sortby;
-- 4. Execute the queries
EXEC sys.sp_executesql #createIndex;
EXEC sys.sp_executesql #executeQuery;
END
GO
You can test like this:
EXEC dbo.DynamicSortProc 'ColA', 'ASC';
EXEC dbo.DynamicSortProc 'ColA', 'DESC';
EXEC dbo.DynamicSortProc 'ColB', 'ASC';
EXEC dbo.DynamicSortProc 'ColB', 'DESC';
In each case they sort as expected and there is no sort in the output query.

Order by Case When ColumnName as Variable

I've seen plenty of examples where people are using CASE WHEN in the ORDER BY clause of a Select statement. Typically, they're comparing the value of a variable to a string of the column name.
This is fine but what about when you have an extremely wide table?
Can you not just say something like
ORDER BY
CASE WHEN #SortDesc = 1 THEN #SortField END DESC,
CASE WHEN #SortDesc = 0 THEN #SortField END ASC
Or do you really really have to have a CASE WHEN for every column in the result set? Edit: Note that this is being converted from a SQL string to plain old SQL so dynamically building and executing it as a string isn't an option.
You can build up a dynamic sql string for something like this:
DECLARE #SortDesc varchar(max)
SELECT #SortDesc = [query to get your Sort column name].
DECLARE #sql varchar(max) = 'SELECT * FROM TABLE ORDER BY ' + #SortDesc
exec(#sql)
Or do you really really have to have a CASE WHEN for every column in
the result set?
ORDER BY
CASE WHEN #SortDesc = 1 THEN #SortField END DESC,
CASE WHEN #SortDesc = 0 THEN #SortField END ASC
The point is that your shown code won't order. #SortField is replaced on all records by the same value, and you have no special order at all.
What you can do is address a #orderBy variable witch controls what realy ocurrs like this:
ORDER BY
CASE WHEN #orderBy = 'NameDesc' THEN name END DESC,
CASE WHEN #orderBy = 'NameAsc' THEN name END,
CASE WHEN #orderBy = 'someDateDesc' THEN someDate END DESC,
CASE WHEN #orderBy = 'someDateAsc' THEN someDate END,
CASE WHEN #orderBy = 'IdDesc' THEN Id END DESC,
CASE WHEN #orderBy = 'IdAsc' THEN Id END
Here you are ordering by six criterias, but five of them are null for all records, so your efecive order is by the selected field. And yes, you will need to write all variants that you want to use.

SQL Case with 2 conditions [duplicate]

This question already has answers here:
T-SQL Conditional Order By
(4 answers)
Closed 8 years ago.
I have a stored procedure that will accept 2 different parameters. The first parameter will determine which column I want to sort on, the second parameter will determine whether it is ASC or DESC
Create Procedure Some_SP
#sortcolumn varchar(10)
#sortorder varchar(10)
AS
Select * from empTable
Order by
CASE #sortcolumn WHEN 'First_Name' THEN fname END,
CASE #sortcolumn WHEN 'Last_Name' THEN lname END,
CASE #sortcolumn WHEN 'ID' THEN empID END,
CASE #sortorder WHEN 'ascending' THEN ASC END,
CASE #sortorder WHEN 'descending' THEN DESC END
It is giving me syntax error. How do I fix it so that I can have 2 conditions in my CASE statement?
The following will work:
Select * from empTable
Order by
CASE WHEN #sortcolumn = 'First_Name' AND #SortOrder = 'ascending' THEN fname END ASC,
CASE WHEN #sortcolumn = 'First_Name' AND #SortOrder = 'descending' THEN fname END DESC
etc...
In order to avoid typing each of these case statements by hand, you could write a "generator" script that you use to create this (especially good if the table definition would change):
SELECT
'CASE WHEN #SortColumn = ''' + C.name + ''' AND #SortOrder = ''ascending'' THEN ' + C.name + ' END ASC,' + CHAR(13) + CHAR(10) +
'CASE WHEN #SortColumn = ''' + C.name + ''' AND #SortOrder = ''descending'' THEN ' + C.name + ' END DESC,'
FROM sys.columns C
WHERE C.object_id = object_id('[Schema].[Table]')
If you want to avoid dynamic SQL and using 2x your conditions, you can use row_number
eg:
declare #t table (string varchar(50), number int)
insert #t values ('a',9),('f',2),('c',1)
declare
#sc varchar(10) = 'number', -- or 'string', etc
#so varchar(10) = 'desc' -- or 'asc'
select *
from
(
select
*,
case #sc when 'string' then ROW_NUMBER() over (order by string)
when 'number' then ROW_NUMBER() over (order by number)
end rn
from #t
) v
order by
case #so when 'desc' then -rn else rn end
You can just copy and paste and run this. I hate dynamic SQL, don't do it.
Unfortunately you'll have to duplicate the query....but it solves your specific problem.
DECLARE
#sortcolumn varchar(10),
#sortorder varchar(10)
SET #sortcolumn = 'fname'
SET #sortorder = 'DESC'
DECLARE
#Data TABLE
(
fname nvarchar(10),
lname nvarchar(10),
empID int
)
INSERT INTO #Data VALUES ('BBB', 'BBB', 2)
INSERT INTO #Data VALUES ('AAA', 'AAA', 1)
IF #sortorder = 'DESC' BEGIN
SELECT
*
FROM
#Data
ORDER BY
CASE
WHEN #sortcolumn = 'fname' THEN fname
WHEN #sortcolumn = 'lname' THEN lname
END
DESC
END ELSE BEGIN
SELECT
*
FROM
#Data
ORDER BY
CASE
WHEN #sortcolumn = 'fname' THEN fname
WHEN #sortcolumn = 'lname' THEN lname
END
END
Modifying Jon's answer to cap the ORDER BY list at just 2 instead of 2 * #columns
SELECT *
FROM MyTable
CROSS APPLY (VALUES
('First_Name',fname),
('Last_Name' ,lname),
('Id' ,ID )
) sort(SortColumn, SortValue)
WHERE SortColumn = #SortColumn
ORDER BY
CASE #SortOrder WHEN 'ascending' THEN SortValue END ASC,
CASE #SortOrder WHEN 'descending' THEN SortValue END DESC

Passing dynamic order by in stored procedure

I am creating below stored procedure.
declare #PageNum as Int
declare #PerPageResult as Int
declare #StartDate as varchar(25)
declare #EndDate as varchar(25)
declare #SortType as Varchar(50)
declare #SortDirection as Varchar(4)
set #PageNum=1
set #PerPageResult=20
set #StartDate='2008-02-08'
set #EndDate='2015-02-08'
set #SortType='RegDate'
set #SortDirection='Desc'
declare #Temp Table(RowNum int, RegDate Date, Registered int, Female int, Male int, [Join] int, Rebill int, TotalPointsEarned int, Expire int)
declare #sort varchar(50)
Insert into #Temp
Select ROW_NUMBER() over (order by #SortType+' '+#SortDirection) As RowNum, * From (
SELECT
CAST(m.registrationdate AS Date) as RegDate,
count(m.id) Registered,
count(CASE WHEN m.gender='F' then 'F' end) As Female,
count(CASE WHEN m.gender='M' then 'M' end) As Male
count(CASE WHEN p.paymenttransactiontype='N' then 'N' end) As [Join],
count(CASE WHEN p.paymenttransactiontype='R' then 'R' end) As Rebill,
count(m.tokensearned) As TotalPointsEarned,
count(CASE WHEN p.paymenttransactiontype='E' then 'E' end) As Expire
from member m
join payment p on m.id=p.id_member
join user_role u on u.member_id=m.id
where u.role_id <> 3
and CAST(m.registrationdate AS Date) > #StartDate and CAST(m.registrationdate AS Date) < #EndDate
GROUP BY CAST(m.registrationdate AS Date)
) as aa
Select * from #Temp Where RowNum>((#PageNum-1)*#PerPageResult) and RowNum<=#PerPageResult * #PageNum
Order by #SortType+' '+#SortDirection
In above when i pass the Order by clause dynamically, its not sorting the data properly but when i write column name explicitly, it works fine. Might be its taking #SortType+' '+#SortDirection as varchar rather than Date
I tried writing Order by case when (#Sort='RegDate' and #SortDirection='Desc') Then RegDate End Desc, but it didn't work
How can i pass order by dynamically here.
Edit:
#Andomar: I tried your provided solution and added one more field for Date type. And it didn't work too.
below is what i did.
create table t1 (id int, name varchar(50), dt date);
insert t1 values
(1, 'Chihiro Ogino','2009-02-08'),
(2, 'Spirit of the Kohaku River','2008-02-08'),
(3, 'Yubaba','2012-02-08');
declare #sortColumn varchar(50) = 'dt'
declare #sortOrder varchar(50) = 'ASC'
select *
from t1
order by
case
when #sortOrder <> 'ASC' then 0
when #sortColumn = 'id' then id
end ASC
, case
when #sortOrder <> 'ASC' then ''
when #sortColumn = 'name' then name
end ASC
, case
when #sortOrder <> 'ASC' then ''
when #sortColumn = 'dt' then name
end ASC
, case
when #sortOrder <> 'DESC' then 0
when #sortColumn = 'id' then id
end DESC
, case
when #sortOrder <> 'DESC' then ''
when #sortColumn = 'name' then name
end DESC
, case
when #sortOrder <> 'DESC' then ''
when #sortColumn = 'dt' then name
end DESC
You can use a complicated order by clause. That requires one case for each sort direction and each data type. With this example dataset:
create table t1 (id int, name varchar(50), created date);
insert t1 values
(1, 'Chihiro Ogino', '2012-01-01'),
(2, 'Spirit of the Kohaku River', '2012-01-03'),
(3, 'Yubaba', '2012-01-02');
You could use an order by clause like:
declare #sortColumn varchar(50) = 'created'
declare #sortOrder varchar(50) = 'DESC'
select *
from t1
order by
case
when #sortOrder <> 'ASC' then 0
when #sortColumn = 'id' then id
end ASC
, case
when #sortOrder <> 'ASC' then ''
when #sortColumn = 'name' then name
end ASC
, case
when #sortOrder <> 'ASC' then cast(null as date)
when #sortColumn = 'created' then created
end ASC
, case
when #sortOrder <> 'DESC' then 0
when #sortColumn = 'id' then id
end DESC
, case
when #sortOrder <> 'DESC' then ''
when #sortColumn = 'name' then name
end DESC
, case
when #sortOrder <> 'DESC' then cast(null as date)
when #sortColumn = 'created' then created
end DESC
Working example at SQL Fiddle.
Another option is to create the query dynamically, and run it with exec. For example:
declare #sql nvarchar(max)
set #sql = 'select * from YourTable order by ' + #sortColumn + ' ' + #sortDir
exec (#sql)
#Andomar's answer help solve a similar issue. I needed to sort on any number of 23 different columns, in any order. I ended up with the following:
create table sorting(ID int, columnName varchar(50), sort varchar(10), position int)
insert into sorting
values(1,'column1','DESC',1),
(1,'column2','ASC',2),
...
(1,'columnN','DESC',N)
Adding parameter #sort to the SP to identify the entries in sorting:
ORDER BY ISNULL(STUFF((SELECT ', ' + a.columnName + ' ' + a.sort
FROM sorting a
WHERE a.ID = #sort
ORDER BY a.position ASC
FOR XML PATH('')), 1, 2, ''),NULL)
There are two basic approaches to building dynamically orderable stored procedures:
Pass in the ORDER BY clause as a parameter to the stored procedure. In the stored procedure, build up the SQL statement in a string and then execute this statement using EXEC or sp_ExecuteSql.
-- This Method is used when your Column names are dynamic
-- We need to create a dynamic query and Execute it as shown below.
CREATE PROCEDURE getEmployees ( #OrderByClause varchar(100) ) AS
-- Create a variable #SQLStatement
DECLARE #SQLStatement varchar(255)
-- Enter the dynamic SQL statement into the
-- variable #SQLStatement
SELECT #SQLStatement = 'SELECT EmployeeID, FirstName, LastName, SSN, Salary
FROM Employees ORDER BY '+ #OrderByClause+''
-- Execute the SQL statement
EXEC(#SQLStatement)
Pass in the column to sort by and then use a CASE statement in the ORDER BY clause to order the results according to the input parameter value.
--This method is used when you column name is not dynamic
SELECT EmployeeID, FirstName, LastName, SSN, Salary
FROM Employees
ORDER BY
CASE WHEN #ColumnName='LastName' THEN LastName
WHEN #ColumnName='Salary' THEN CONVERT(varchar(50), Salary)
WHEN #ColumnName='SSN' THEN SSN
END
something like this should work :
ORDER BY
CASE WHEN #SortDirection = 'ASC' THEN #SortType END ASC,
CASE WHEN #SortDirection = 'DESC' THEN #SortType END DESC

What is the best way to paginate results in SQL Server

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)?
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;
https://learn.microsoft.com/en-us/sql/t-sql/queries/select-order-by-clause-transact-sql#using-offset-and-fetch-to-limit-the-rows-returned
Key points to consider when using it:
ORDER BY is mandatory to use OFFSET ... FETCH clause.
OFFSET clause is mandatory with FETCH. You cannot use ORDER BY ...
FETCH.
TOP cannot be combined with OFFSET and FETCH in the same query
expression.
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.
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(*).
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;
TechNet: Paging a Query with SQL Server
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
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.
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.
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
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)
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;
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;
From 2012 onward we can use
OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY
This is a duplicate of the 2012 old SO question:
efficient way to implement paging
FROM [TableX]
ORDER BY [FieldX]
OFFSET 500 ROWS
FETCH NEXT 100 ROWS ONLY
Here the topic is discussed in greater details, and with alternate approaches.
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.
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
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.
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
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
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