SQL Case Order By specific order and Direction - sql

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,
. . .

Related

The text, ntext, and image data types cannot be compared RowNumber

i know that this problem has been resolved many times but after tens forum i cannot find my mistake...
I'm on 2008 SQL Server
Here is my query:
Declare #order varchar(MAX) = 'DESC'
Declare #sort varchar(MAX) = 'Description'
SELECT TOP 10 * from ( SELECT *, ROW_NUMBER() OVER
(ORDER BY
(CASE WHEN #sort = 'Name' and #order = 'ASC' THEN NAME
WHEN #sort = 'URL' and #order = 'ASC' THEN URL
WHEN #sort = 'Description' and #order = 'ASC' THEN Description
END) ASC,
(CASE WHEN #sort = 'Name' and #order = 'DESC' THEN NAME
WHEN #sort = 'URL' and #order = 'DESC' THEN URL
WHEN #sort = 'Description' and #order = 'DESC' THEN Description
END) DESC)
AS RowNum FROM Application) AS MyDerivedTable WHERE MyDerivedTable.RowNum BETWEEN 0 AND 10
And the error:
"The text, ntext, and image data types cannot be compared or sorted,
except when using IS NULL or LIKE operator"
I also tried to replace varchar by nvarchar but it doesnt worked.
Thanks for help
A case expression returns a single type. I would recommend that you rewrite the order by using separate expressions:
ORDER BY (CASE WHEN #sort = 'Name' and #order = 'ASC' THEN NAME END) ASC,
(CASE WHEN #sort = 'URL' and #order = 'ASC' THEN URL END) ASC,
(CASE WHEN #sort = 'Description' and #order = 'ASC' THEN Description END) ASC,
(CASE WHEN #sort = 'Name' and #order = 'DESC' THEN NAME END) DESC,
(CASE WHEN #sort = 'URL' and #order = 'DESC' THEN URL END) DESC,
(CASE WHEN #sort = 'Description' and #order = 'DESC' THEN Description END) DESC
I'm not sure if this will fix your particular problem, which seems due to arcane types being used for certain columns. However, this fixes most of the problems that arise from using a single case expression with mixed types.
Presumably, you are using text columns. This type has been deprecated, so you should not use it. I would strongly recommend that you change the type to varchar(max) or nvarchar(max).
SOLVED ! I Had a 'URL' as type 'text' in my Table definition, I replaced it by nvarchar. Thank you

SQL multiple case statement

How can I make this correct? I am getting an error saying:
Incorrect syntax near the keyword 'DESC'.
SELECT *
FROM Companies
Order By
CASE #OrderByField
WHEN 'CompanyName' THEN CompanyName
WHEN 'CreatedDate' THEN CreatedDate
END,
CASE #Direction
WHEN 'DESC' THEN DESC
WHEN 'ASC' THEN ASC
END
Can I not have two case statements? If not, how can i pass in the name of the order by field and direction as parameters?
Thanks!
Another problem surface after the first one is solved...
If I include a field that doesn't have the datatype of string, if throws an error.
For example:
SELECT *
FROM Companies
Order By
CASE #Direction WHEN 'DESC' THEN
CASE #OrderByField
WHEN 'CompanyName' THEN CompanyName
WHEN 'CreatedDate' THEN CreatedDate
WHEN 'Score' THEN Score
END
END DESC,
CASE #Direction WHEN 'ASC' THEN
CASE #OrderByField
WHEN 'CompanyName' THEN CompanyName
WHEN 'CreatedDate' THEN CreatedDate
WHEN 'Score' THEN Score
END
END ASC
#OrderByField is type of nvarchar(50)
assume Score has a datatype of float.
Above throws an error like the one below even if i am not trying to order by the score field. Error converting data type nvarchar to float.
Similarly, including a createddate throws an error: Conversion failed when converting date and/or time from character string.
Will be very appreciated if anyone can help out.
You can't return a keyword from a case statement.
But you can achieve what you want by ordering in two ways, but return a constant expression for the order you don't want:
SELECT *
FROM Companies
Order By
CASE #Direction WHEN 'DESC' THEN
CASE #OrderByField
WHEN 'CompanyName' THEN CompanyName
WHEN 'CreatedDate' THEN CreatedDate
END
END DESC,
CASE #Direction WHEN 'ASC' THEN
CASE #OrderByField
WHEN 'CompanyName' THEN CompanyName
WHEN 'CreatedDate' THEN CreatedDate
END
END ASC
Non-matching cases will return null and so will be ignored for ordering purposes.
As ASC and DESC are keywords, you can't have them as return value from a CASE.
You can make two cases, one for ASC and one for DESC:
SELECT *
FROM Companies
Order By
CASE
WHEN #OrderByField = 'CompanyName' AND #Direction = 'ASC' THEN CompanyName
WHEN #OrderByField = 'CreatedDate' AND #Direction = 'ASC' THEN CreatedDate
END ASC,
CASE
WHEN #OrderByField = 'CompanyName' AND #Direction = 'DESC' THEN CompanyName
WHEN #OrderByField = 'CreatedDate' AND #Direction = 'DESC' THEN CreatedDate
END DESC
Note that all values from a case has to have the same data type, so you might need one pair of cases for string and one pair for dates.

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.

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)

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.