SQL Server 2014 - Incorrect syntax near OFFSET - sql

I'm using SQL Server 2014 management studio and I wanted to create a stored procedure but I have a problem at the very end of the code where the error "Incorrect syntax near OFFSET" appears.
CREATE PROCEDURE [dbo].[GetGamesPlayed]
#gameSession int,
#datestart datetime,
#dateend datetime,
#orderBy nvarchar(50) = 'email',
#sortOrder nvarchar(4) = 'asc',
#search nvarchar(50) = '',
#startRow int = 0,
#rows int = 2147483647
AS
BEGIN
SET NOCOUNT ON;
SELECT t.*,
COUNT(*) OVER() AS filteredRows,
(SELECT COUNT(id) FROM gameplayed WHERE gameplayed.id_GameSession = #gameSession) AS totalRows
FROM (SELECT
DISTINCT GamesTable.[Game ID],
GamesTable.[Rep Id],
GamesTable.[User Id],
GamesTable.[Date],
GamesTable.[Email],
GamesTable.[Name],
GamesTable.[Phone Number],
GamesTable.[Tokens Played],
CASE
WHEN prizewon.pending = 1 THEN 'Pending'
WHEN prizewon.skillTestingQCorrect = 1 AND prizewon.rulesAccepted = 1 THEN 'Yes'
ELSE 'No'
END AS [Won]
FROM PrizeWon
RIGHT OUTER JOIN (
SELECT
GamePlayed.id AS [Game ID],
GamePlayed.playedOn AS [Date],
users.repId AS [Rep Id],
users.id AS [User Id],
users.fullName AS [Name],
users.email AS [Email],
users.phoneNumber AS [Phone Number],
GamePlayed.tokensPlayed AS [Tokens Played]
FROM GamePlayed
INNER JOIN Users on GamePlayed.id_Users = Users.id
WHERE GamePlayed.id_GameSession = #gameSession) AS GamesTable ON GamesTable.[Game ID] = prizewon.id_GamePlayed
WHERE GamesTable.Date >= #datestart
AND GamesTable.Date <= #dateend
AND ( GamesTable.[Email] LIKE '%' + #search + '%'
OR GamesTable.[Rep Id] LIKE '%' + #search + '%'
OR GamesTable.[Name] LIKE '%' + #search + '%'
OR GamesTable.[Game ID] LIKE '%' + #search + '%'
)
) t
--order by cases must be split into datatypes
ORDER BY
CASE WHEN #sortOrder = 'asc' THEN
CASE #orderBy -- nvarchar
WHEN 'repid' THEN t.[Rep Id]
WHEN 'name' THEN t.[Name]
WHEN 'email' THEN t.[Email]
WHEN 'phone' THEN t.[Phone Number]
WHEN 'won' THEN t.Won
END
END ASC,
CASE WHEN #sortOrder = 'asc' THEN
CASE #orderBy --date
WHEN 'date' THEN t.Date
END
END ASC,
CASE WHEN #sortOrder = 'asc' THEN
CASE #orderBy --int
WHEN 'id' THEN t.[Game ID]
WHEN 'tokens' THEN t.[Tokens Played]
END
END ASC,
CASE WHEN #sortOrder = 'desc' THEN
CASE #orderBy -- nvarchar
WHEN 'repid' THEN t.[Rep Id]
WHEN 'name' THEN t.[Name]
WHEN 'email' THEN t.[Email]
WHEN 'phone' THEN t.[Phone Number]
WHEN 'won' THEN t.Won
END
END DESC,
CASE WHEN #sortOrder = 'desc' THEN
CASE #orderBy --date
WHEN 'date' THEN t.Date
END
END DESC,
CASE WHEN #sortOrder = 'desc' THEN
CASE #orderBy --int
WHEN 'id' THEN t.[Game ID]
WHEN 'tokens' THEN t.[Tokens Played]
END
END DESC
OFFSET #startRow ROWS FETCH NEXT #rows ROWS ONLY
END
I have seen in other posts that the error may appear because the OFFSET is not after an ORDER BY, but in this case it is after one ORDER BY.
Another thing that I saw is that SQL Server 2008 doesn't support OFFSET, but I'm using 2014.
I have also checked that the variables that I'm using are declared and they all are...
Its pretty much the only error that is keeping me from creating the procedure. If anyone could help I'll be very greatful!

After a little research I used "SELECT ##version" and found out that my database version was 2008, which doesn't support OFFSET.
A quick update to version 2012 solved the issue.

Related

Error in Order by clause when using case statement in SQL Server

I have a stored procedure defined as below. When I execute it, I get an error and couldn't come up with a solution.
ALTER PROCEDURE [dbo].[GetShippmentList]
#shipmentNumber VARCHAR(20),
#requestType VARCHAR(1),
#shipmentNames VARCHAR(100),
#assigneeDateFrom DATETIME,
#assignedDateTo DATETIME,
#completedDateFrom DATETIME,
#completedDateto DATETIME,
#status VARCHAR(20),
#userId VARCHAR(20),
#pageNo int,
#pageSize int,
#sortField VARCHAR(20),
#sortOrder VARCHAR(4)
AS
BEGIN
SET NOCOUNT ON
IF OBJECT_ID('tempdb..##ApTemp') IS NULL
BEGIN
CREATE TABLE ##ApTemp(INST_ID VARCHAR(32),
STATUS NVARCHAR(16),
NAME NVARCHAR(64),
ASSIGNED_DATE DATETIME NULL,
COMPLETED_DATE DATETIME NULL,
USER_ID NVARCHAR(64) )
INSERT INTO ##ApTemp(INST_ID, STATUS, NAME, ASSIGNED_DATE, COMPLETED_DATE, USER_ID)
SELECT
w.INST_ID, w.STATUS, w.NAME, w.ASSIGNED_DATE,
w.COMPLETED_DATE, w.USER_ID
FROM
WestShipment.Shipmentt_Prod.dbo.WShipments w
WHERE
w.NAME IN ('T1', 'T2', 'T5', 'T51', 'T3', 'T31')
AND w.STATUS NOT IN ('Removed', 'Cancelled')
AND w.APP IN ('East', 'West')
END
AS (
SELECT w.INST_ID, w.NAME, w.ASSIGNED_DATE AS AssignedDate, w.COMPLETED_DATE AS CompletedDate,w.STATUS AS WfStatus, w.USER_ID AS User_Id,
STUFF(
(SELECT '','' + t.USER_ID
FROM ##ApTemp t
WHERE t.INST_ID = w.Inst_ID AND t.NAME = w.Name
FOR XML PATH ('')), 1, 1, '') AS Assignees
FROM ##ApTemp AS w
WHERE (w.NAME IN( #shipmentNames) OR LEN(#shipmentNames) > 4)
AND ( w.STATUS IN (#status) OR LEN(#status) > 4)
AND ((w.ASSIGNED_DATE BETWEEN #assigneeDateFrom AND #assignedDateTo) OR LEN(#assigneeDateFrom) >4 OR LEN(#assignedDateTo) > 4)
AND ((w.COMPLETED_DATE BETWEEN #completedDateFrom AND #completedDateto) OR LEN(#completedDateFrom) >4 OR LEN(#completedDateto) >4)
AND (w.User_ID LIKE #userId OR LEN(#userId) > 4 )
GROUP BY w.NST_ID, w.NAME , w.ASSIGNED_DATE, w.COMPLETED_DATE,w.STATUS, User_Id
)
INSERT INTO #tempTable(INST_ID, NAME, AssignedDate, CompletedDate, WfStatus, Assignees, ShippmentNumber , ShipmentName , RequestType )
SELECT w.INST_ID, w.NAME, w.AssignedDate, w.CompletedDate, w.WfStatus, w.Assignees, m.DocumentNumber, m.ShipmentName, m.RequestType
FROM dbo.DncMain m INNER JOIN workflows w ON m.InstanceId = w.INST_ID
WHERE (m.ShipmentNumber = #shipmentNumber OR LEN(#shipmentNumber) > 4)
AND(m.RequestType = #requestType OR #requestType NOT LIKE '0')
IF #sortOrder = 'DESC'
begin
SELECT INST_ID, NAME, AssignedDate, CompletedDate, WfStatus, Assignees, ShipmentNumber, ShipmentName FROM #tempTable
ORDER BY CASE #sortField
WHEN 'ShipmentNumber' THEN ShipmentNumber
WHEN 'TaskName' THEN NAME
WHEN 'ShipmentName' THEN ShipmentName
WHEN 'AssignedDate' THEN AssignedDate
WHEN 'CompletedDate' THEN CompletedDate
WHEN 'Assignees' THEN Assignees
WHEN 'Status' THEN WfStatus
END DESC OFFSET (#pageNo) ROWS FETCH NEXT(#pageSize) ROW ONLY
end
ELSE
begin
SELECT TOP(#pageNo) INST_ID, NAME, AssignedDate, CompletedDate, WfStatus, Assignees, ShipmentNumber, ShipmentName FROM #tempTable
ORDER BY CASE #sortField
WHEN 'TaskName' THEN NAME
WHEN 'ShipmentName' THEN ShipmentName
WHEN 'ShipmentNumber' THEN ShipmentNumber
WHEN 'AssignedDate' THEN AssignedDate
WHEN 'CompletedDate' THEN CompletedDate
WHEN 'Assignees' THEN Assignees
WHEN 'Status' THEN WfStatus
END ASC OFFSET (#pageNo) ROWS FETCH NEXT(#pageSize) ROW ONLY
end
SELECT COUNT(1) AS TotalRows FROM #tempTable
END
GO
The error I got is :
Msg 8115, Level 16, State 2, Procedure GetShipmentList, Line 60
Arithmetic overflow error converting expression to data type datetime.
The problem looks like in the order by clause. If I remove the case statement from the ORDER BY clause, it works fine.
This is an order by:
ORDER BY CASE #sortField
WHEN 'TaskName' THEN NAME
WHEN 'ShipmentName' THEN ShipmentName
WHEN 'ShipmentNumber' THEN ShipmentNumber
WHEN 'AssignedDate' THEN AssignedDate
WHEN 'CompletedDate' THEN CompletedDate
WHEN 'Assignees' THEN Assignees
WHEN 'Status' THEN WfStatus
END ASC
This is a single expression in SQL. It returns one specific type -- regardless of which WHEN clause is being executed. That causes a problem.
So, another way of writing the logic is to split this to different case statements:
ORDER BY (CASE #sortField WHEN 'TaskName' THEN NAME END) ASC,
(CASE #sortField WHEN 'ShipmentName' THEN ShipmentName END) ASC,
(CASE #sortField WHEN 'ShipmentNumber' THEN ShipmentNumber END) ASC,
(CASE #sortField WHEN 'AssignedDate' THEN AssignedDate END) ASC,
(CASE #sortField WHEN 'CompletedDate' THEN CompletedDate END) ASC,
(CASE #sortField WHEN 'Assignees' THEN Assignees END) ASC,
(CASE #sortField WHEN 'Status' THEN WfStatus END) ASC
This puts each column as a separate key, so you cannot get conflicts.
You are trying to convert a number to a datetime, which you cannot do. First, you must convert it to a string (varchar).
For example:
declare #IntegerDate int = 20151223
select cast(#IntegerDate as datetime) //this will throw an error
select cast(cast(#IntegerDate as varchar) as datetime) //This will work
Another approach is to use dynamic SQL to generate different SQL for the different choices, allowing the optimizer to choose the best plan for each.
DECLARE #ComputedSortField SYSNAME = CASE #sortField
WHEN 'ShipmentNumber' THEN N'ShipmentNumber'
WHEN 'TaskName' THEN N'NAME'
WHEN 'ShipmentName' THEN N'ShipmentName'
WHEN 'AssignedDate' THEN N'AssignedDate'
WHEN 'CompletedDate' THEN N'CompletedDate'
WHEN 'Assignees' THEN N'Assignees'
WHEN 'Status' THEN N'WfStatus'
END
DECLARE #TopClause NVARCHAR(13) = CASE #SortOrder
WHEN 'DESC' THEN N''
ELSE N'TOP(#pageNo)'
END
DECLARE #ComputedSortOrder NVARCHAR(4) = CASE #SortOrder
WHEN 'DESC' THEN N'DESC'
ELSE N'ASC'
END
DECLARE #Sql NVARCHAR(MAX) = N'SELECT ' + #TopClause + N' INST_ID, NAME, AssignedDate,
CompletedDate, WfStatus, Assignees, ShipmentNumber, ShipmentName
FROM #tempTable
ORDER BY ' + #ComputedSortField + N' ' + #ComputedSortOrder + N'
OFFSET (#pageNo) ROWS FETCH NEXT(#pageSize) ROW ONLY'
PRINT #Sql
EXECUTE sp_executesql #Statement = #Sql
, #Params = N'#pageNo int, #pageSize int'
, #PageNo = #PageNo
, #PageSize = #PageSize
Note that all the pieces being concatenated together are defined within the code. There is nothing from a parameter that is being concatenated into the string to avoid SQL injection attacks.
If #SortField does not match one of the defined sorts #ComputedSortField will be null causing #Sql to be null. You may want to define a default #ComputedSortField or make an #OrderByClause that can be set to '', removing sorting, when a proper #SortField value is passed.

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

T-SQL Conditional Order By

I am trying to write a stored procedure that returns a list of object with the sort order and sort direction selected by the user and passed in as sql parameters.
Lets say I have a table of products with the following columns: product_id(int), name(varchar), value(int), created_date(datetime)
and parameters #sortDir and #sortOrder
I want to do something like
select *
from Product
if (#sortOrder = 'name' and #sortDir = 'asc')
then order by name asc
if (#sortOrder = 'created_date' and #sortDir = 'asc')
then order by created_date asc
if (#sortOrder = 'name' and #sortDir = 'desc')
then order by name desc
if (#sortOrder = 'created_date' and #sortDir = 'desc')
then order by created_date desc
I tried do it with case statements but was having problems since the data types were different. Anyone got any suggestions?
CASE is an expression that returns a value. It is not for control-of-flow, like IF. And you can't use IF within a query.
Unfortunately, there are some limitations with CASE expressions that make it cumbersome to do what you want. For example, all of the branches in a CASE expression must return the same type, or be implicitly convertible to the same type. I wouldn't try that with strings and dates. You also can't use CASE to specify sort direction.
SELECT column_list_please
FROM dbo.Product -- dbo prefix please
ORDER BY
CASE WHEN #sortDir = 'asc' AND #sortOrder = 'name' THEN name END,
CASE WHEN #sortDir = 'asc' AND #sortOrder = 'created_date' THEN created_date END,
CASE WHEN #sortDir = 'desc' AND #sortOrder = 'name' THEN name END DESC,
CASE WHEN #sortDir = 'desc' AND #sortOrder = 'created_date' THEN created_date END DESC;
An arguably easier solution (especially if this gets more complex) is to use dynamic SQL. To thwart SQL injection you can test the values:
IF #sortDir NOT IN ('asc', 'desc')
OR #sortOrder NOT IN ('name', 'created_date')
BEGIN
RAISERROR('Invalid params', 11, 1);
RETURN;
END
DECLARE #sql NVARCHAR(MAX) = N'SELECT column_list_please
FROM dbo.Product ORDER BY ' + #sortOrder + ' ' + #sortDir;
EXEC sp_executesql #sql;
Another plus for dynamic SQL, in spite of all the fear-mongering that is spread about it: you can get the best plan for each sort variation, instead of one single plan that will optimize to whatever sort variation you happened to use first. It also performed best universally in a recent performance comparison I ran:
http://sqlperformance.com/conditional-order-by
You need a case statement, although I would use multiple case statements:
order by (case when #sortOrder = 'name' and #sortDir = 'asc' then name end) asc,
(case when #sortOrder = 'name' and #sortDir = 'desc' then name end) desc,
(case when #sortOrder = 'created_date' and #sortDir = 'asc' then created_date end) asc,
(case when #sortOrder = 'created_date' and #sortDir = 'desc' then created_date end) desc
Having four different clauses eliminates the problem of converting between types.
There are multiple ways of doing this. One way would be:
SELECT *
FROM
(
SELECT
ROW_NUMBER() OVER ( ORDER BY
CASE WHEN #sortOrder = 'name' and #sortDir = 'asc' then name
END ASC,
CASE WHEN #sortOrder = 'name' and #sortDir = 'desc' THEN name
END DESC,
CASE WHEN i(#sortOrder = 'created_date' and #sortDir = 'asc' THEN created_date
END ASC,
CASE WHEN i(#sortOrder = 'created_date' and #sortDir = 'desc' THEN created_date
END ASC) RowNum
*
)
order by
RowNum
You can also do it using dynamic sql.
declare #str varchar(max)
set #str = 'select * from Product order by ' + #sortOrder + ' ' + #sortDir
exec(#str)

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

Dynamic order by without using dynamic 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.