I am trying to do pagination in SQL SERVER and I am getting two errors on OFFSET and ROWS
CREATE PROCEDURE XYZ
#offset int,
#limit int,
#order char(4),
#Id int
AS
BEGIN
SELECT * FROM TABLE
WHERE ID = #id
ORDER BY
CASE WHEN #order = 'desc' THEN [TIME] END DESC,
CASE WHEN #order = 'asc' THEN [TIME] END ASC
*OFFSET* #offset FETCH ROWS NEXT #limit *ROWS* ONLY
END
GO
Error on OFFSET:
Incorrect Syntax near 'OFFSET'.
Error on ROWS (Second one):
Incorrect Syntax near 'ROWS'. Expecting FROM.
Can someone help me out with this ?
Well, in SQL Server 2008 you obviously can't use the fetch...next clause that was introduced in 2012.
However, this doesn't mean you can't do pagination.
One simple way is to use a cte with row_number:
;WITH CTE AS
(
SELECT *, -- Don't be lazy, specify the Columns list...
ROW_NUMBER() OVER
(
ORDER BY
CASE WHEN #order = 'desc' THEN [TIME] END DESC,
CASE WHEN #order = 'asc' THEN [TIME] END ASC
) As rn
FROM TABLE
WHERE ID = #id
)
SELECT * -- Don't be lazy, specify the Columns list...
FROM CTE
WHERE rn >= #offset
AND rn <= #offset + #limit
Related
Why doesn't this code work? I get this error
Error near ')'
on the last line. I cannot see where I made a syntax error (this is for SQL Server 2017).
DECLARE #NumRows INT;
SELECT #NumRows = COUNT(*) / 2
FROM SAMA;
SELECT MAX(NoMonths)
FROM
(SELECT TOP(#NumRows) NoMonths
FROM SAMA
ORDER BY NoMonths ASC)
What the query is trying to do, is to find a max of top N rows, and the N is defined in the variable.
Without MAX, the subquery works and returns N rows. But when I add MAX, it fails.
You have to set a table alias:
DECLARE #NumRows INT;
SELECT #NumRows = COUNT(*) / 2 FROM SAMA;
SELECT MAX(NoMonths)
FROM (
SELECT TOP(#NumRows) NoMonths
FROM SAMA
ORDER BY NoMonths ASC
) table_alias
You need to have a table alias
DECLARE #NumRows INT;
SELECT #NumRows = COUNT(*) / 2 FROM SAMA;
SELECT MAX(NoMonths)
FROM (SELECT TOP(#NumRows) NoMonths FROM SAMA ORDER BY NoMonths ASC) sama_alias
I'm reviewing some stored procedures in a 3rd party app my employer has purchased. They whole system runs slow as molasses.
Every single select stored procedure has the most convoluted select I've ever seen. And practically EVERY stored procedure is written this way. (StackOverflow needs a WTF hash tag too.)
Before I gut these and make appropriate changes to the code, has anyone ever seen stored procedures coded this way?
Code works, but it seems like it there are 20 better ways to do it. Using Linq in code to sort the data set for one. Making just one call to the DB, and putting the (smallish) result set in a session variable.
create PROCEDURE dbo.sp_REALLYWEIRD
(
#parameter1 varchar(5), #parameter2 varchar(5), #parameter3 varchar(5),
#sortField varchar(20), #sortDirection int, #OrgIdentifier varchar(5),
#startRowIndex int, #maximumRows int, #rowCount int OUTPUT
)
AS
BEGIN
DECLARE #returnValue TABLE (
Id int NOT NULL,
SomeColumn1 char(7) NOT NULL,
SomeColumn2 datetime NULL,
SomeColumn3 datetime NULL,
SomeColumn4 varchar(18) NOT NULL,
SomeColumn5 smallint NOT NULL,
RowNumber int NOT NULL)
DECLARE #endRowIndex int
SET #endRowIndex = #startRowIndex + #maximumRows
IF #sortField IS NULL
SET #sortField = 'SomeColumnName'
INSERT INTO #returnValue
(Id, SomeColumn1, SomeColumn2, SomeColumn3,
SomeColumn4, SomeColumn5, RowNumber)
SELECT Id, SomeColumn1, SomeColumn2, SomeColumn3,
SomeColumn4, SomeColumn5, RowNumber
FROM (SELECT Id,SomeColumn1, SomeColumn2, SomeColumn3,
SomeColumn4, SomeColumn5,
CASE #sortField
WHEN 'SomeColumnName1' THEN
CASE #sortDirection
WHEN 1 THEN Row_Number() OVER (PARTITION BY DistrictLea ORDER BY SomeColumn1) - 1
ELSE Row_Number() OVER (PARTITION BY DistrictLea ORDER BY SomeColumn1 DESC) - 1
END
WHEN 'SomeColumnName2' THEN
CASE #sortDirection
WHEN 1 THEN Row_Number() OVER (PARTITION BY DistrictLea ORDER BY SomeColumn2) - 1
ELSE Row_Number() OVER (PARTITION BY DistrictLea ORDER BY SomeColumn2 DESC) - 1
END
WHEN 'SomeColumnName3' THEN
CASE #sortDirection
WHEN 1 THEN Row_Number() OVER (PARTITION BY DistrictLea ORDER BY SomeColumn3) - 1
ELSE Row_Number() OVER (PARTITION BY DistrictLea ORDER BY SomeColumn3 DESC) - 1
END
WHEN 'SomeColumnName4' THEN
CASE #sortDirection
WHEN 1 THEN Row_Number() OVER (PARTITION BY DistrictLea ORDER BY SomeColumn4) - 1
ELSE Row_Number() OVER (PARTITION BY DistrictLea ORDER BY SomeColumn4 DESC) - 1
END
END AS RowNumber
FROM dbo.someTable
WHERE OrgIdentifier = #OrgIdentifier) t
SELECT #rowCount = COUNT(*)
FROM #returnValue
SELECT Id, SomeColumn1, SomeColumn2, SomeColumn3,
SomeColumn4, SomeColumn5
FROM #returnValue
WHERE RowNumber >= #startRowIndex
AND RowNumber < #endRowIndex
ORDER BY RowNumber
END
Seems like a lot of work just to load a data grid.
I have below stored procedure in sql server 2016, its working fine there.
Now I need to create the same sp in sql 2008, now I am getting error :
Msg 102, Level 15, State 1, Procedure GetEmployees, Line 41 [Batch
Start Line 0] Incorrect syntax near 'OFFSET'. Msg 153, Level 15, State
2, Procedure GetEmployees, Line 42 [Batch Start Line 0] Invalid usage
of the option NEXT in the FETCH statement.
How to modify the same proc so that it can run over sql 2008 as well.
--dbo.GetEmployees '',2,2
CreatePROCEDURE [dbo].GetEmployees
(
#SearchValue NVARCHAR(50) = '',
#PageNo INT = 0,
#PageSize INT = 10,
#SortColumn NVARCHAR(20) = 'Name',
#SortOrder NVARCHAR(20) = 'ASC'
)
AS BEGIN
SET NOCOUNT ON;
if #PageNo<0 set #PageNo=0
set #PageNo=#PageNo+1
SET #SearchValue = LTRIM(RTRIM(#SearchValue))
Set #SearchValue= nullif(#SearchValue,'')
; WITH CTE_Results AS
(
SELECT EmployeeID, Name, City from tblEmployee
WHERE (#SearchValue IS NULL OR Name LIKE '%' + #SearchValue + '%')
ORDER BY
CASE WHEN (#SortColumn = 'EmployeeID' AND #SortOrder='ASC')
THEN EmployeeID
END ASC,
CASE WHEN (#SortColumn = 'EmployeeID' AND #SortOrder='DESC')
THEN EmployeeID
END DESC,
CASE WHEN (#SortColumn = 'Name' AND #SortOrder='ASC')
THEN Name
END ASC,
CASE WHEN (#SortColumn = 'Name' AND #SortOrder='DESC')
THEN Name
END DESC,
CASE WHEN (#SortColumn = 'City' AND #SortOrder='ASC')
THEN City
END ASC,
CASE WHEN (#SortColumn = 'City' AND #SortOrder='DESC')
THEN City
END DESC
OFFSET #PageSize * (#PageNo - 1) ROWS
FETCH NEXT #PageSize ROWS ONLY
),
CTE_TotalRows AS
(
select count(EmployeeID) as MaxRows from tblEmployee WHERE (#SearchValue IS NULL OR Name LIKE '%' + #SearchValue + '%')
)
Select MaxRows TotalRecords, t.EmployeeID, t.Name, t.City,t.Department,t.Gender from dbo.tblEmployee as t, CTE_TotalRows
WHERE EXISTS (SELECT 1 FROM CTE_Results WHERE CTE_Results.EmployeeID = t.EmployeeID)
OPTION (RECOMPILE)
END
You need a row_number() window function and in the OVER section you want to put your entire sorting expression. Note that I've created another CTE for readability, but you could get the same thing done with just a subquery.
Formatted code for the SELECT statement would be the following:
WITH CTE_Rownums AS (
SELECT
EmployeeID,
Name,
City,
row_number() over ( ORDER BY ... ) as rn -- put your entire order by here
FROM tblEmployee
WHERE
#SearchValue IS NULL
OR Name LIKE '%' + #SearchValue + '%'
), CTE_Results AS (
SELECT EmployeeID, Name, City
FROM CTE_Rownums
WHERE
(rn > #PageSize * (#PageNo - 1)
AND (rn <= #PageSize * #PageNo)
ORDER BY rn
), CTE_TotalRows AS (
SELECT count(EmployeeID) as MaxRows
FROM tblEmployee
WHERE
#SearchValue IS NULL
OR Name LIKE '%' + #SearchValue + '%'
)
SELECT MaxRows TotalRecords, t.EmployeeID, t.Name, t.City,t.Department,t.Gender
FROM dbo.tblEmployee as t
CROSS JOIN CTE_TotalRows
WHERE EXISTS (
SELECT 1
FROM CTE_Results
WHERE CTE_Results.EmployeeID = t.EmployeeID
)
OPTION (RECOMPILE)
In the last SELECT I've replaced comma separated where clause with CROSS JOIN.
If you use 2008 R2 or older you can't use OFFSET FETCH,
you have alternative to use ROW_NUMBER() and rewrite your query for examle
with OFFSET
SELECT Price
FROM dbo.Inventory
ORDER BY Price OFFSET 10 ROWS FETCH NEXT 5 ROWS ONLY
this query without OFFSET using ROW_NUMBER()
SELECT Price
FROM
(
SELECT Price
ROW_NUMBER() OVER (ORDER BY Price) AS Seq
FROM dbo.Inventory
)t
WHERE Seq BETWEEN 11 AND 15
I have the following scenario: a table with up to 20000 entries and another table with corresponding custom fields.
I need to implement a query with a filter opportunity over all colums (including the custom fields) AND skip and take AND I need the total row count after filtering.
With the help of dynamic sql I managed to implement a query which adds the custom fields as columns to the first table.
But I am really having troubles implementing a skip and take functionality which runs really fast AND also returns the total row count.
As not all of our customers are running on SQL Server 2012, the best way would be to implement the take and skip via a where clause over the row_number, but i think this also is the slowest alternative. I like the OFFSET and FETCH functionality for the 2012 case, but still getting the row count seems to slow down a lot.
this is my query resulting from the dynamic sql, below the whole dynamic query, both with the two alternatives as comments
With tempTable AS
(
SELECT *
--1. ALTERNATIVE:
,ROW_NUMBER() Over (ORDER BY Nachname, Vorname desc) As ROW_NUMBER ,COUNT(1) OVER () as Total_Rows
FROM
(
SELECT [a].*, [DFSF].[Datenfeld_Name],[Datenfeld_Inhalt]
FROM
[dbo].[Ansprechpartner] AS a left join [dbo].[Datenfeld] AS dfe
ON [a].[Id] = [dfe].[Datenfeld_AnsprechpartnerID]
left join
[Datenfeld_Standardfelder] AS DFSF
ON dfe.[StandardfeldID] = [DFSF].[id] and datenfeld_kategorie = 'Ansprechpartner'
) AS j
PIVOT
(
max([Datenfeld_Inhalt]) FOR [j].[Datenfeld_Name] IN ([Medium],[Kontaktthema],[Mediengattung],[Medienthema],[E-Mail],[Homepage],[Rolle])
) AS p
)
SELECT *
--2. ALTERNATIVE:
--,COUNT(1) OVER ()
FROM tempTable
WHERE 1=1
-- 1. ALTERNATIVE:
and Row_Number BETWEEN 0 AND 100
ORDER BY Nachname, Vorname DESC
--2. ALTERNATIVE:
OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY
;
and following the whole dynamic query.
I actually think, there is an error in it, because like this I won't get the correct row count, I would probably have to call it once again without the row_number filter to get it right...
DECLARE #filterExpression nvarchar(MAX)
DECLARE #showOnlyDoublets int
DECLARE #sortExpression nvarchar(MAX)
DECLARE #skip AS int
DECLARE #take AS [int]
SELECT #skip = 0
SELECT #take = 100
--SELECT #filterExpression = 'WHERE Vorname like ''%luc%'''
SELECT #filterExpression = ' WHERE 1=1'
SELECT #sortExpression = 'ORDER BY Nachname, Vorname desc'
SELECT #showOnlyDoublets = 0
DECLARE #idList nvarchar(MAX)
select #idList = COALESCE(#idList + '],[', '[') + [DFSF].[Datenfeld_Name] from
[Datenfeld_Standardfelder] AS DFSF
where datenfeld_kategorie = 'Ansprechpartner'
SELECT #idList = #idList +']'
--SELECT #idList
DECLARE #sqlToRun nvarchar(max)
SET #sqlToRun =
'With tempTable As
(
SELECT *
, ROW_NUMBER() Over (' + #sortExpression + ') As Row_Number
FROM
(
SELECT [a].*, [DFSF].[Datenfeld_Name],[Datenfeld_Inhalt]--, CAST( ROW_NUMBER() OVER(ORDER BY [DFSF].[Datenfeld_Name] DESC) AS varchar(20))
FROM
[dbo].[Ansprechpartner] AS a left join [dbo].[Datenfeld] AS dfe
ON [a].[Id] = [dfe].[Datenfeld_AnsprechpartnerID]
left join
[Datenfeld_Standardfelder] AS DFSF
ON dfe.[StandardfeldID] = [DFSF].[id] and datenfeld_kategorie = ''Ansprechpartner''
) AS j
PIVOT
(
max([Datenfeld_Inhalt]) FOR [j].[Datenfeld_Name] IN (' + #idList + ')
) AS p
)
SELECT *, COUNT(*) OVER () as Total_Rows FROM tempTable
' + #filterExpression + '
AND Row_Number BETWEEN ' + CAST ( #skip AS varchar ) + ' AND ' + CAST ( #take AS varchar ) + '
' + #sortExpression + '
--OFFSET ' + CAST ( #skip AS varchar ) + ' ROWS FETCH NEXT ' + CAST ( #take AS varchar ) + ' ROWS ONLY
;'
PRINT #sqlToRun
EXECUTE sp_executesql #sqlToRun
So my question is: is there a way to improve this query (one of the two alternatives)? Or do you have a totally different idea, because I think, either way, if I call the count correctly, it will cost a lot of time.
Friede,
Take at look at this stored proc below. The comments I have added can hopefully help.
It offers a total row count for the entire query regardless of how many records are being returned per page or what page the user is on. What is probably disturbing your number of rows is that the row numbers are part of the where clause in your code. See in this example how to return subsets without having to handle that logic in the where clause.
Your performance issues are probably related to indexing and pivoting. I see you are filtering a column by arbitrary text input. You'll need that column indexed with all the other returned columns included. An alternative that might speed everything up is described in the query below in which you first find all the ID's that match your predicate (with a non-clustered index on the searched column and including the primary key ID column) then you join the actual query to that table. This limits the entire operation, including the pivot, to only the rows that match.
CREATE PROC [api].[cc_pcp_member_search]
(
#string VARCHAR(255)
,#plan_long_id BIGINT
,#num_rows BIGINT
,#page_num BIGINT
,#order_by VARCHAR(255) = 'last_name'
,#sort_order VARCHAR(10) = 'ASC'
)
AS
SET NOCOUNT ON
DECLARE #MemberList AS TABLE (MemberId INT)
INSERT INTO #MemberList
/* What is happening in the EXEC statement below, is that I am running a text search against
very specific indexes that only return the ID's(clustered) of the items I care about.
This has proven to be far more performant than applying the text predicate to the entire
set due to indexing reasons. The 'real' query further below that brings back all the needed fields
uses this list of ID's to define the scope of the set. You might consider something similar if
you are querying aginst arbitrary text values.
*/
EXEC dbo.uspGetMemberIdForMemberSearch #string
/* The table variable does slow things down and you might not need it at all.
In this case, the developers were using Entity Framework and it was having
trouble determining the signature and data types of the stored procedure without
a typed result set
*/
DECLARE #ResultSet AS TABLE ( member_id BIGINT,
first_name VARCHAR(255),
last_name VARCHAR(255),
full_name VARCHAR(255),
hicn VARCHAR(255),
gender VARCHAR(255),
plan_id BIGINT,
plan_name VARCHAR(255),
phone_1 VARCHAR(255),
phone_2 VARCHAR(255),
dob DATETIME,
total_records INT)
INSERT INTO #ResultSet
SELECT
CAST(M.MemberId AS BIGINT) AS member_id,
M.FirstName AS first_name,
M.LastName AS last_name,
CONCAT(M.FirstName, ' ', M.LastName) AS full_name,
M.HICN AS hicn,
CASE WHEN M.Gender = 0 THEN 'F' ELSE 'M' END AS gender,
CAST(P.VirtusPlanId AS BIGINT) AS plan_id,
P.PlanName AS plan_name,
M.PhoneNumber1 AS phone_1,
M.PhoneNumber2 AS phone_2,
M.DateOfBirth AS dob,
CAST(0 AS INT) AS total_records --<<< This is the place holder for total count, see further below
FROM
Member M
INNER JOIN [Plan] P
ON M.PlanId = P.PlanId
INNER JOIN #MemberList ML --<<< This is where the final filtering on ID's happens
ON M.MemberId = ML.MemberId
/* Heres the core of what you are probably dealing with. The proc allows the caller
to specify sort order, query string, records per page, and current page in the set.
The users will usually search by name, sort by last name, and the grid then pages
through those results 10 rows (defaulted in app) at a time.
*/
DECLARE #InputPageNumber int = #page_num
DECLARE #RowsPerPage INT = #num_rows
DECLARE #RealPageNumber INT = #InputPageNumber + 1 --<<< 0 based index adjustment
DECLARE #OrderBy VARCHAR(255) = #order_by
DECLARE #SortOrder VARCHAR(4) = #sort_order
SELECT
member_id
,first_name
,last_name
,full_name
,hicn
,gender
,plan_id
,plan_name
,phone_1
,phone_2
,dob
/* Here is your total row count of the set regardless of how many
rows are being paged at the moment. Because OVER() is counting the
filtered set in the table variable only, it is fast. It would do the same
with a CTE. You're already using the same thing.
*/
,CAST(COUNT(*) OVER() AS INT) AS total_records
FROM #ResultSet
WHERE plan_id = #plan_long_id
/* In this usecase, the output of this query feeds a paged web grid. Below is the
logic used to determine the sort column at execution time. I'm not pretending it's
pretty but it works.
*/
ORDER BY CASE
WHEN #OrderBy = 'first_name' AND #SortOrder = 'ASC'
THEN first_name
WHEN #OrderBy = 'last_name' AND #SortOrder = 'ASC'
THEN last_name
WHEN #OrderBy = 'full_name' AND #SortOrder = 'ASC'
THEN full_name
WHEN #OrderBy = 'hicn' AND #SortOrder = 'ASC'
THEN hicn
WHEN #OrderBy = 'plan_name' AND #SortOrder = 'ASC'
THEN plan_name
WHEN #OrderBy = 'gender' AND #SortOrder = 'ASC'
THEN gender
END ASC,
CASE
WHEN #OrderBy = 'first_name' AND #SortOrder = 'DESC'
THEN first_name
WHEN #OrderBy = 'last_name' AND #SortOrder = 'DESC'
THEN last_name
WHEN #OrderBy = 'full_name' AND #SortOrder = 'DESC'
THEN full_name
WHEN #OrderBy = 'hicn' AND #SortOrder = 'DESC'
THEN hicn
WHEN #OrderBy = 'plan_name' AND #SortOrder = 'DESC'
THEN plan_name
WHEN #OrderBy = 'gender' AND #SortOrder = 'DESC'
THEN gender
END DESC
OFFSET (#RealPageNumber - 1) * #RowsPerPage ROWS -- dynamic offset arithmetic to convert requested page number to row offset
FETCH NEXT #RowsPerPage ROWS ONLY; -- dynamic number of rows to display per page
I am working on Sybase Adaptive Server Enterprise (version 12.5.0.3). Trying to use Row_number() OVER (Partition by columnname order by columnname). When I execute the query it is throwing an exception saying that the syntax near OVER is incorrect. I have searched for proper row_number() syntax for sybase database, but there is nothing wrong in the syntax. I guess that the Sybase version that am using does not support row_number() OVER. I even tried dense_rank() OVER, but am getting the same error.
I need to know whether it is really a syntax issue or its because of Sybase's low version which is not supporting the key words?
If the issue is with the version, then is there any alternative for row_number OVER and dense_rank() OVER for sybase database.
My Query:
select cr.firstname, cr.lastname, cr.dob,cr.phone,
row_number() over (patition by cr.dob order by createddate) "rank"
from ff.CrossReferenceTable cr
Error Message:
Server Message: Number 156, Severity 15
Server 'MyServer', Line 1:
Incorrect syntax near the keyword 'over'.
Right, unfortunately Sybase ASE doesn't support row_number() function as well as rank() and dense_rank().
However, in some simple cases, where partition clause is not used it could be converted in the way like
select rank=identity(music), * into #new_temp_tab1 from CrossReferenceTable order by createddate
select firstname, lastname, dob, phone, rank from #new_temp_tab1
In your case it's going to be a little bit more complicated, I can recommend using cursor with temporary table to emulate row_number() over partition by behavior.
Please have a look at the example below:
create table CrossReferenceTable
(
firstname varchar(50),
lastname varchar(50),
dob int,
phone char(10),
createddate date
)
go
create proc sp_CrossReferenceTable
as
begin
declare #i int
declare #cur_firstname varchar(50)
declare #cur_lastname varchar(50)
declare #cur_dob int
declare #cur_phone varchar(10)
declare #cur_rank int
declare cur cursor for
select
cr.firstname,
cr.lastname,
cr.dob,
cr.phone,
count(*) AS "rank"
from
CrossReferenceTable cr
group by
cr.dob
order by
cr.dob,
createddate
CREATE TABLE #CrossReferenceTable_TEMP
(
firstname varchar(50),
lastname varchar(50),
dob int,
phone char(10),
rank INT
)
open cur
fetch cur into
#cur_firstname,
#cur_lastname,
#cur_dob,
#cur_phone,
#cur_rank
set #i = #cur_rank
while ##SQLSTATUS = 0
begin
if #i = 0
set #i = #cur_rank
insert into #CrossReferenceTable_TEMP
select
#cur_firstname,
#cur_lastname,
#cur_dob,
#cur_phone,
case
when #cur_rank > 1 then #cur_rank - (#i - 1)
ELSE #cur_rank
end as "rank"
set #i = #i - 1
fetch cur into
#cur_firstname,
#cur_lastname,
#cur_dob,
#cur_phone,
#cur_rank
end
select
firstname,
lastname,
dob,
phone,
rank
from
#CrossReferenceTable_TEMP
end
go
exec sp_CrossReferenceTable
Try the generic query below to have same effect as ROW_NUMBER()
SELECT
A.MyPartitionColumn,
A.MyRunningNumberColumn,
( SELECT count(*)
FROM MyTable
WHERE MyPartitionColumn = A.MyPartitionColumn
AND MyRunningNumberColumn <= A.MyRunningNumberColumn
) AS "Row_Number"
FROM MyTable A
ORDER BY MyPartitionColumn, MyRunningNumberColumn