SQL Server dynamic sorting on multiple columns - sql

I have a stored procedure which uses dynamic sorting, 2 parameters determine the sorting - column: #SortIndex and sort direction: #SortDirection
relevant code:
...
ROW_NUMBER() OVER
(
ORDER BY
-- string order by
CASE #SortDirection
WHEN 'ASC' THEN
CASE #SortIndex
WHEN 1 THEN SKU
WHEN 2 THEN BrandName
WHEN 3 THEN ItemName
END
END ASC,
CASE #SortDirection
WHEN 'DESC' THEN
CASE #SortIndex
WHEN 1 THEN SKU
WHEN 2 THEN BrandName
WHEN 3 THEN ItemName
END
END DESC,
This sorts on single columns, but I want to sort on BrandName ASC, ItemName ASC when #SortIndex is 2.

If you cannot use Dynamic SQL, the only way is to list all the possible combination for ASC and DESC
For example:
ORDER By
CASE WHEN #SortIndex = '1' AND #SortDirection = 'ASC' THEN SKU END,
CASE WHEN #SortIndex = '1' AND #SortDirection = 'DESC' THEN SKU END DESC,
CASE WHEN #SortIndex = '2' AND #SortDirection = 'ASC' THEN BrandName END,
CASE WHEN #SortIndex = '2' AND #SortDirection = 'DESC' THEN BrandName END DESC,
--and so on...

ROW_NUMBER() OVER
(
ORDER BY
-- string order by
CASE #SortDirection
WHEN 'ASC' THEN
CASE #SortIndex
WHEN 1 THEN SKU
WHEN 2 THEN BrandName + ',' + ItemName
WHEN 3 THEN ItemName
END
END ASC,
CASE #SortDirection
WHEN 'DESC' THEN
CASE #SortIndex
WHEN 1 THEN SKU
WHEN 2 THEN BrandName + ',' + ItemName
WHEN 3 THEN ItemName
END
END DESC,
Use Brandname + ItemName in the When 2 Clause and to have both fields be used in the sort.

A sample for generality....By K.AryaeeMoeen
---------------------------------------------------
SELECT 1 AS Num, '2015-06-22' AS Datex INTO Arya
INSERT INTO Arya
SELECT 2, '2015-08-17' UNION SELECT 3, '2015-07-14'
---------------------------------------------------
Now, Dynamic sorting(Base on Datex Field) in a SELECTION ....
---------------------------------------------------
SELECT Num, Date1 FROM ARYA, (SELECT -1 as e union Select 1 as e) a
WHERE a.e=-1 --(OR a.e=1) For Dynamic Sort
ORDER BY DATEDIFF(DAY, '2000-01-01', Arya.Datex)*sign(a.e)
---------------------------------------------------

Related

SQL Case Order By specific order and Direction

I have a table that I need sorted on fields By #SortBy and #SortDirection,
for ID,PriorityID,stateType (column type is int) result is OK, but for Title (nvarchar) Query Result is:
Conversion failed when converting the nvarchar value 'Title Column value' to data type int.
Query:
CASE
WHEN #SortDirection = 'ASC' THEN
CASE #SortBy
WHEN 'ID' THEN ID --int
WHEN 'Title' THEN Title --nvarchar
WHEN 'PriorityID' THEN [Priority] --int
WHEN 'stateType' THEN [state] --int
end
END ASC
,case WHEN #SortDirection = 'DESC' THEN
CASE #SortBy
WHEN 'ID' THEN ID
WHEN 'Title' THEN Title
WHEN 'Priority' THEN [Priority]
WHEN 'state' THEN [state]
END
END DESC
The types are different, and that is a problem for the case expressions. The simplest method is a different case for each possibility:
ORDER BY (CASE WHEN #SortDirection = 'ASC' AND #SortBy = 'ID' THEN ID END) ASC,
(CASE WHEN #SortDirection = 'ASC' AND #SortBy = 'Title' THEN Title END) ASC,
. . .

SQL Server, ordering with variables

I understand that I have to use cases to be able to use variables, but how can I make one that includes direction as well (asc, desc) preferably with if/else or something similar.
I have a variable called #orderDirection for this purpose. Which can have either asc or desc as the value.
ORDER BY
CASE WHEN #order = 1 THEN [date_time] END,
CASE WHEN #order = 2 THEN [company] END,
CASE WHEN #order = 3 THEN [country_1] END,
CASE WHEN #order = 4 THEN [country_2] END,
[date_time]
I would like to have else included in this, so if it isn't desc then it should always be ascending order, even if this value is something else entirely.
Something like this:
IF #orderDirection = 'desc' THEN
ORDER BY
CASE WHEN #order = 1 THEN [date_time] END desc,
CASE WHEN #order = 2 THEN [company] END desc,
CASE WHEN #order = 3 THEN [country_1] END desc,
CASE WHEN #order = 4 THEN [country_2] END desc,
[date_time]
ELSE
ORDER BY
CASE WHEN #order = 1 THEN [date_time] END asc,
CASE WHEN #order = 2 THEN [company] END asc,
CASE WHEN #order = 3 THEN [country_1] END asc,
CASE WHEN #order = 4 THEN [country_2] END asc,
[date_time]
You can do that with dynamic SQL. But with regular SQL, list out each option:
ORDER BY (CASE WHEN #order = 1 AND #orderDirection = 'desc' THEN [date_time] END ) desc,
(CASE WHEN #order = 2 AND #orderDirection = 'desc' THEN [company] END) desc,
(CASE WHEN #order = 3 AND #orderDirection = 'desc' THEN [country_1] END) desc,
(CASE WHEN #order = 4 AND #orderDirection = 'desc' THEN [country_2] END) desc,
(CASE WHEN #order = 1 AND #orderDirection = 'asc' THEN [date_time] END ),
(CASE WHEN #order = 2 AND #orderDirection = 'asc' THEN [company] END),
(CASE WHEN #order = 3 AND #orderDirection = 'asc' THEN [country_1] END),
(CASE WHEN #order = 4 AND #orderDirection = 'asc' THEN [country_2] END),
[date_time]
Put your query in a CTE and create an integer ID field, then order by that field either positively or negatively:
declare #orderDirection varchar(4) = 'asc'
,#order int = 1
;with a as(
select ROW_NUMBER() OVER (ORDER BY
CASE WHEN #order = 1 THEN [date_time] END,
CASE WHEN #order = 2 THEN [company] END,
CASE WHEN #order = 3 THEN [country_1] END,
CASE WHEN #order = 4 THEN [country_2] END,
[date_time]
) AS ID
... --other fields
FROM [table]
)
SELECT
*
FROM a
ORDER BY (ID * case when #orderDirection = 'asc' then 1 else -1 end)

Optimizing this 40+ second select query on MSSQL 2012?

I'm no DBA and I'm out of ideas on optimizing this query. It's taking roughly 40+ seconds to run. Any glaring newbie mistakes where I could optimize?
USE [deskcal2014]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROC [dbo].[proc_AdminRegisteredCards]
(
#Take AS INT,
#Skip AS INT,
#FilterColumn AS NVARCHAR(max),
#FilterOrder AS NVARCHAR(max))
AS
BEGIN
SELECT TOP(#Take)
ISNULL( ROW_NUMBER() OVER(ORDER BY ta.CreatedOn, ta.ItemId), -1000) AS AdminRegisteredCardsId,
ta.ItemId,
ta.CardNumber,
ta.FirstName,
ta.LastName,
ta.Birthday,
ta.PostalCode,
ta.[Description],
ta.CardActivated,
ta.ContactInfo,
ta.PhoneNumber,
ta.ReceiveCalendarReminders,
ta.ReceiveGeneralMails,
ta.ReceivePrefStoreMails,
ta.CardStatus,
ta.SamoaCardId,
ta.CalendarUserId,
ta.LiveOpsRegistrantId,
ta.UseType,
ta.CreatedOn,
ta.ModifiedBy,
ta.ModifiedOn from (
SELECT CalendarUser.CalendarUserId as ItemId,
SamoaCard.CardNumber,
SamoaCard.FirstName,
SamoaCard.LastName,
CalendarUser.Birthday,
CalendarUser.PostalCode,
RegisterSourceType.[Description],
CalendarUserCard.CardActivated,
CalendarUser.EmailAddress as ContactInfo,
CalendarUser.PhoneNumber,
CalendarUser.ReceiveCalendarReminders,
CalendarUser.ReceiveGeneralMails,
CalendarUser.ReceivePrefStoreMails,
CASE WHEN CalendarUserCard.CardDeactivated IS NOT NULL THEN 'Deactivated' ELSE 'Activated' END AS CardStatus,
SamoaCard.SamoaCardId,
CalendarUser.CalendarUserId,
null as LiveOpsRegistrantId,
SamoaCard.CreatedOn,
'C' as UseType,
CalendarUser.ModifiedBy,
CalendarUser.ModifiedOn
FROM (
(dbo.CalendarUser CalendarUser
INNER JOIN dbo.RegisterSourceType RegisterSourceType ON (CalendarUser.RegisterType = RegisterSourceType.RegisterType))
INNER JOIN dbo.CalendarUserCard CalendarUserCard ON (CalendarUserCard.CalendarUserId = CalendarUser.CalendarUserId)
)
INNER JOIN dbo.SamoaCard SamoaCard ON (CalendarUserCard.SamoaCardId = SamoaCard.SamoaCardId)
ORDER BY
case when #FilterColumn = 'FirstName' and #FilterOrder = 'ASC'
then CalendarUser.Firstname end asc,
case when #FilterColumn = 'FirstName' and #FilterOrder = 'DESC'
then CalendarUser.Firstname end desc,
case when #FilterColumn = 'LastName' and #FilterOrder = 'ASC'
then CalendarUser.Lastname end asc,
case when #FilterColumn = 'LastName' and #FilterOrder = 'DESC'
then CalendarUser.Lastname end desc,
case when #FilterColumn = 'CardNumber' and #FilterOrder = 'ASC'
then CalendarUser.CardNumber end asc,
case when #FilterColumn = 'CardNumber' and #FilterOrder = 'DESC'
then CalendarUser.CardNumber end desc,
case when #FilterColumn = 'Birthday' and #FilterOrder = 'ASC'
then CalendarUser.Birthday end asc,
case when #FilterColumn = 'Birthday' and #FilterOrder = 'DESC'
then CalendarUser.Birthday end desc,
case when #FilterColumn = 'Description' and #FilterOrder = 'ASC'
then RegisterSourceType.[Description] end asc,
case when #FilterColumn = 'Description' and #FilterOrder = 'DESC'
then RegisterSourceType.[Description] end desc,
case when #FilterColumn = 'ContactInfo' and #FilterOrder = 'ASC'
then CalendarUser.EmailAddress end asc,
case when #FilterColumn = 'ContactInfo' and #FilterOrder = 'DESC'
then CalendarUser.EmailAddress end desc,
case when #FilterColumn = 'CardActivated' and #FilterOrder = 'ASC'
then CalendarUserCard.CardActivated end asc,
case when #FilterColumn = 'CardActivated' and #FilterOrder = 'DESC'
then CalendarUserCard.CardActivated end desc,
case when #FilterColumn = 'PostalCode' and #FilterOrder = 'ASC'
then CalendarUser.PostalCode end asc,
case when #FilterColumn = 'PostalCode' and #FilterOrder = 'DESC'
then CalendarUser.PostalCode end desc
OFFSET #Skip ROWS -- skip N rows
FETCH NEXT #Take ROWS ONLY
union all
SELECT TOP(10)
LiveOpsRegistrant.LiveOpsRegistrantId as ItemId,
LiveOpsRegistrant.CardNumber,
'Registered' as FirstName,
'Card' as LastName,
LiveOpsRegistrant.Birthday,
null as PostalCode,
'LiveOps' as Description,
LiveOpsRegistrant.CreatedOn as CardActivated,
LiveOpsRegistrant.PhoneNumber as ContactInfo,
LiveOpsRegistrant.PhoneNumber,
CONVERT(bit,0) as ReceiveCalendarReminders,
CONVERT(bit,0) as ReceiveGeneralMails,
CONVERT(bit,0) as ReceivePrefStoreMails,
'Activated' AS CardStatus,
SamoaCard.SamoaCardId,
null as CalendarUserId,
LiveOpsRegistrant.LiveOpsRegistrantId,
SamoaCard.CreatedOn,
'L' as UseType,
SamoaCard.ModifiedBy,
SamoaCard.ModifiedOn
FROM dbo.LiveOpsRegistrant LiveOpsRegistrant
INNER JOIN dbo.SamoaCard SamoaCard ON (LiveOpsRegistrant.CardNumber = SamoaCard.CardNumber)) ta
END
GO
Echoing some of the comments already given: Having a bunch of logic in an ORDER BY clause usually doesn't work well. ROW_NUMBER() can be nasty when used in a query with many joins and other complexities as in your case.
Temp tables are probably your first best option here. Reading your code, I think the first one is for CalendarUser.CalendarUserId, and you'll want to populate it with a bunch of nested if ... else if statements:
if #FilterColumn = 'FirstName' and #FilterOrder = 'ASC'
begin
insert into #CalendarUser
select top(#Take) CalendarUserId
order by Firstname asc
offset #Skip rows
fetch next #Take rows only
end
else
begin
if .....
Populate a second temp table #DataOut with all the fields you want to output, using an inner join on #CalendarUser to filter the result set. Exclude the field you're calculating with ROW_NUMBER(). Leave the UNION ALL out of this query, append the data from that into #DataOut table as a separate step.
The final output query will be
select
ISNULL( ROW_NUMBER() OVER(ORDER BY CreatedOn, ItemId), -1000)
AS AdminRegisteredCardsId,
#DataOut.*
from #DataOut
Not pleasant to write, feels brute force, but I'm fairly sure you'll see a dramatic performance improvement.
I've been trying to optimize a similar query and came to the conclusion that ordering and filtering across joins has a big impact on performance - my advice would be to denormalize everything that you sort by or filter on using an indexed view and seeing what impact this has on the performance.

ORDER BY with a CASE statement for column with alias

I need a stored procedure which will allow me to return sorted results based on two input parameters: #sortColumnName and #sortDirection. I wrote the following stored procedure, but when I run it, I am getting this error: "Invalid column name 'LastPayCheckDate'."
SELECT Name, SUM(Pay), MAX(PayCheckDate) as LastPayCheckDate
FROM Employee
GROUP BY Name
ORDER BY
CASE WHEN #sortColumnName = 'LastPayCheckDate' AND #sortDirection = 'ASC'
THEN [LastPayCheckDate] END ASC,
CASE WHEN #sortColumnName = 'LastPayCheckDate' AND #sortDirection = 'DESC'
THEN [LastPayCheckDate] END DESC
What is going on? I suppose that t-sql runs the case statement before the select... Am I right? How can I work around this issue?
Thanks for the help!
Try this
ORDER BY
CASE WHEN #sortColumnName = 'LastPayCheckDate' AND #sortDirection = 'ASC'
THEN MAX(PayCheckDate) END ASC,
CASE WHEN #sortColumnName = 'LastPayCheckDate' AND #sortDirection = 'DESC'
THEN MAX(PayCheckDate) END DESC
Example
create table Test (id int, somevalue int)
insert Test values(1,1)
insert Test values(2,1)
insert Test values(3,2)
insert Test values(3,2)
insert Test values(4,2)
run this in 1 shot
declare #sortDirection char(4)
select #sortDirection = 'DESC'
select somevalue, COUNT(*)
from Test
group by somevalue
order by case when #sortDirection = 'ASC'
then COUNT(*) end asc,
case when #sortDirection = 'DESC'
then COUNT(*) end desc
select #sortDirection = 'ASC'
select somevalue, COUNT(*)
from Test
group by somevalue
order by case when #sortDirection = 'ASC'
then COUNT(*) end asc,
case when #sortDirection = 'DESC'
then COUNT(*) end desc
You need to either use the function again or use a subquery if you want to be able to refer to the column alias.
Also, I think that you need to make sure that all of your columns in the case statement get converted to the same data type.

Dynamic order direction

I writing a SP that accepts as parameters column to sort and direction.
I don't want to use dynamic SQL.
The problem is with setting the direction parameter.
This is the partial code:
SET #OrderByColumn = 'AddedDate'
SET #OrderDirection = 1;
…
ORDER BY
CASE WHEN #OrderByColumn = 'AddedDate' THEN CONVERT(varchar(50), AddedDate)
WHEN #OrderByColumn = 'Visible' THEN CONVERT(varchar(2), Visible)
WHEN #OrderByColumn = 'AddedBy' THEN AddedBy
WHEN #OrderByColumn = 'Title' THEN Title
END
You could have two near-identical ORDER BY items, one ASC and one DESC, and extend your CASE statement to make one or other of them always equal a single value:
ORDER BY
CASE WHEN #OrderDirection = 0 THEN 1
ELSE
CASE WHEN #OrderByColumn = 'AddedDate' THEN CONVERT(varchar(50), AddedDate)
WHEN #OrderByColumn = 'Visible' THEN CONVERT(varchar(2), Visible)
WHEN #OrderByColumn = 'AddedBy' THEN AddedBy
WHEN #OrderByColumn = 'Title' THEN Title
END
END ASC,
CASE WHEN #OrderDirection = 1 THEN 1
ELSE
CASE WHEN #OrderByColumn = 'AddedDate' THEN CONVERT(varchar(50), AddedDate)
WHEN #OrderByColumn = 'Visible' THEN CONVERT(varchar(2), Visible)
WHEN #OrderByColumn = 'AddedBy' THEN AddedBy
WHEN #OrderByColumn = 'Title' THEN Title
END
END DESC
You can simplify the CASE by using ROW_NUMBER which sorts your data and effectively converts it into a handy integer format. Especially since the question is tagged SQL Server 2005
This also expands easily enough to deal with secondary and tertiary sorts
I've used multiplier to again simplify the actual select statement and reduce the chance of RBAR evaluation in the ORDER BY
DECLARE #multiplier int;
SELECT #multiplier = CASE #Direction WHEN 1 THEN -1 ELSE 1 END;
SELECT
Columns you actually want
FROM
(
SELECT
Columns you actually want,
ROW_NUMBER() OVER (ORDER BY AddedDate) AS AddedDateSort,
ROW_NUMBER() OVER (ORDER BY Visible) AS VisibleSort,
ROW_NUMBER() OVER (ORDER BY AddedBy) AS AddedBySort,
ROW_NUMBER() OVER (ORDER BY Title) AS TitleSort
FROM
myTable
WHERE
MyFilters...
) foo
ORDER BY
CASE #OrderByColumn
WHEN 'AddedDate' THEN AddedDateSort
WHEN 'Visible' THEN VisibleSort
WHEN 'AddedBy' THEN AddedBySort
WHEN 'Title' THEN TitleSort
END * #multiplier;
This works fine for me – (where, order by, direction, Pagination)
parameters
#orderColumn int ,
#orderDir varchar(20),
#start int ,
#limit int
select * from items
order by
CASE WHEN #orderColumn = 0 AND #orderdir = 'desc' THEN items.[CategoryName] END DESC,
CASE WHEN #orderColumn = 0 AND #orderdir = 'asc' THEN items.[CategoryName] END ASC,
CASE WHEN #orderColumn = 1 AND #orderdir = 'desc' THEN items.[CategoryValue] END DESC,
CASE WHEN #orderColumn = 1 AND #orderdir = 'asc' THEN items.[CategoryValue] END ASC,
CASE WHEN #orderColumn = 2 AND #orderdir = 'desc' THEN items.[CreatedOn] END DESC,
CASE WHEN #orderColumn = 2 AND #orderdir = 'asc' THEN items.[CreatedOn] END ASC
OFFSET #start ROWS FETCH NEXT #limit ROWS ONLY
Here is an example:
CREATE PROCEDURE GetProducts
(
#OrderBy VARCHAR(50),
#Input2 VARCHAR(30)
)
AS
BEGIN
SET NOCOUNT ON
SELECT Id, ProductName, Description, Price, Quantity
FROM Products
WHERE ProductName LIKE #Input2
ORDER BY
CASE
WHEN #OrderBy = 'ProductNameAsc' THEN ProductName
END ASC,
CASE
WHEN #OrderBy = 'ProductNameDesc' THEN ProductName
END DESC
END
From here:
http://www.dominicpettifer.co.uk/Blog/21/dynamic-conditional-order-by-clause-in-sql-server-t-sql
Ascending and Descending actions need
to be grouped into separate CASE
statements, separated with a comma. In
your server-side code/script make sure
to append 'Asc' or 'Desc' onto the
order by string, or you could have two
Stored procedure input parameters for
column name and order by direction if
you want.
More compact version of accepted answer, but as accepted answer this works fine only when result expressions after THEN have the same type.
ORDER BY
CASE #OrderDirection WHEN 0 THEN
CASE #sortColumn
WHEN 'AddedDate' THEN CONVERT(varchar(50), AddedDate)
WHEN 'Visible' THEN CONVERT(varchar(2), Visible)
WHEN 'AddedBy' THEN AddedBy
WHEN 'Title' THEN Title
END
END ASC,
CASE #OrderDirection WHEN 1 THEN
CASE #sortColumn
WHEN 'AddedDate' THEN CONVERT(varchar(50), AddedDate)
WHEN 'Visible' THEN CONVERT(varchar(2), Visible)
WHEN 'AddedBy' THEN AddedBy
WHEN 'Title' THEN Title
END
END DESC
Dynamic sorting in either ASC or DESC order, irrespective of datatype.
The first example sorts alphabetically, the second using numbers. The #direction variable denotes sort direction (0 = ASC or 1 = DESC) and [column] is the sort column.
This also works for multi-column sorting and you can hide the [row] column if placed in a further outer query.
DECLARE #direction BIT = 1 -- 0 = ASC or 1 = DESC
-- Text sort.
SELECT
IIF(#direction = 0, ROW_NUMBER() OVER (ORDER BY [column] ASC), ROW_NUMBER() OVER (ORDER BY [column] DESC)) [row]
, *
FROM
( -- your dataset.
SELECT N'B' [column]
UNION SELECT N'C'
UNION SELECT N'A'
) [data] ORDER BY [row]
-- Numeric sort.
SELECT
IIF(#direction = 0, ROW_NUMBER() OVER (ORDER BY [column] ASC), ROW_NUMBER() OVER (ORDER BY [column] DESC)) [row],
*
FROM
( -- your dataset.
SELECT 2 [column]
UNION SELECT 3
UNION SELECT 1
) [data] ORDER BY [row]