Order converting to int - sql

I have this order by clause in my stored procedure
ORDER BY
CASE WHEN #sortDir = 2 THEN
CASE
WHEN #sortCol = 'ProductNumber' THEN ProductId
WHEN #sortCol = 'ProductName' THEN CAST(V.[Name] AS NVARCHAR)
...
END
END DESC
I wanted to sort via the column given the #sortCol
It's giving me this error
Conversion failed when converting the nvarchar value 'Product 28' to
data type int.
Why?
How do I fix this?

The "problem" is that case is an expression that returns a single type. And ints beat out strings.
Instead of worrying about types, use separate expressions:
ORDER BY (CASE WHEN #sortDir = 2 AND #sortCol = 'ProductNumber' THEN ProductId END) DESC,
(CASE WHEN #sortDir = 2 AND #sortCol = 'ProductName' THEN v.Name END) DESC,
. . .
This is a bit more verbose, but it avoids all type conversions.

Related

Arithmetic overflow error When sorting by [TimeStamp] (column name)

I'm getting the error:
Arithmetic overflow error converting expression to data type smalldatetime
Please note that I've already searched this topic, but my bug is unique because it is caused by the ORDER BY clause. the problematic line(s) is
WHEN 'TimeStamp' THEN [MyTable].[TimeStamp].
when I comment those TimeStamp, the query executed successfully, but I still need a solution for sorting my TimeStamp column (renaming the column is not possible).
This is my query:
SELECT
Id,
[TimeStamp],
StatusCode
FROM
(
SELECT
TOP (#EndRecord) ROW_NUMBER() OVER
(
ORDER BY
CASE #SortDirection
WHEN 'Desc' THEN
CASE #SortColumn
WHEN 'Id' THEN [MyTable].[Id]
WHEN 'TimeStamp' THEN [MyTable].[TimeStamp]
WHEN 'StatusCode' THEN [MyTable].[StatusCode]
ELSE [MyTable].[TimeStamp]
END
END DESC,
CASE #SortDirection
WHEN 'Asc' THEN
CASE #SortColumn
WHEN 'Id' THEN [MyTable].[Id]
WHEN 'TimeStamp' THEN [MyTable].[TimeStamp]
WHEN 'StatusCode' THEN [MyTable].[StatusCode]
ELSE [MyTable].[TimeStamp]
END
END ASC
) AS 'Row',
[MyTable].[Id] AS 'Id',
[MyTable].[TimeStamp] AS 'TimeStamp',
[MyTable].[StatusCode] AS 'StatusCode'
FROM
[MyTable]
WHERE
(#Filter_Id is null or [MyTable].[Id] = #Filter_Id) AND
(#Filter_StatusCode is null or [MyTable].[StatusCode] = #Filter_StatusCode) AND
(#Filter_FromTimeStamp IS NULL OR [MyTable].[TimeStamp] >= #Filter_FromTimeStamp) AND
(#Filter_TillTimeStamp IS NULL OR [MyTable].[TimeStamp] <= #Filter_TillTimeStamp)
) AS t1
WHERE
Row >=#StartRecord AND
Row <= #EndRecord
A CASE expression has to return a value of a particular type. All THEN clauses have to agree as to the type or allow the data type precedence rules to determine the overall type and apply conversions.
Since you probably don't want to e.g. convert everything to strings and perform ordering operations based on the textual sorting rules, you need to split out your CASE expressions so that you're not forcing any conversions between types.
E.g.
ORDER BY
CASE WHEN #SortDirection='Desc' and #SortColumn = 'Id'
THEN [MyTable].[Id]
END DESC,
CASE WHEN #SortDirection='Desc' and #SortColumn = 'TimeStamp'
THEN [MyTable].[TimeStamp]
END DESC,
CASE WHEN #SortDirection='Desc' and #SortColumn='StatusCode'
THEN [MyTable].[StatusCode]
END DESC,
CASE WHEN #SortDirection='Desc' and #SortColumn not in ('Id','TimeStamp','StatusCode')
THEN [MyTable].[TimeStamp]
END DESC,
And similarly for your ascending sorts.

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.

Stored procedure and bad data type nvarchar to numeric

I have a problem with my stored procedure in SQL Server 2012.
First the table :
[ProductId] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](100) NOT NULL,
[Description] [nvarchar](500) NOT NULL,
[Category] [nvarchar](50) NOT NULL,
[Price] [decimal](16, 2) NOT NULL,
and the stored procedure :
CREATE procedure [dbo].[GetProductsPaging]
(#PageOffset bigint=0,
#PageSize bigint=5,
#OrderBy varchar(30) = 'ProductId',
#DescOrder bit = 0)
AS
BEGIN
IF #DescOrder = 0
BEGIN
SELECT * FROM [dbo].Products
ORDER BY
CASE #OrderBy
WHEN 'ProductId' THEN ProductId
WHEN 'Name' THEN Name
WHEN 'Category' THEN Category
WHEN 'Price' THEN Price
ELSE NULL
END ASC
OFFSET #PageOffset ROWS
FETCH NEXT #PageSize ROWS ONLY
END
ELSE
BEGIN
SELECT * FROM [dbo].Products
ORDER BY
CASE #OrderBy
WHEN 'ProductId' THEN ProductId
WHEN 'Name' THEN Name
WHEN 'Category' THEN Category
WHEN 'Price' THEN Price
ELSE NULL
END DESC
OFFSET #PageOffset ROWS
FETCH NEXT #PageSize ROWS ONLY
END
END
When I execute it like this
EXEC dbo.GetProductsPaging 0, 0 ,'Price', 0
the procedure works just fine, but when using this
EXEC dbo.GetProcedurePaging 0, 0, 'Name', 0
I get this error
Error converting data type nvarchar to numeric.
Someone can help me with this ? Thank for any answers.
All output options from a case must be of the same datatype, I think the case decides what output type to use from the first option, and attempts to cast the other values to that type, In your case, the first and last options, (ProductId and Price), are numeric, buit the others are character... modify the case (in both places) as follows:
CASE #OrderBy
WHEN 'ProductId' THEN str(ProductId, 7, 0)
WHEN 'Name' THEN Name
WHEN 'Category' THEN Category
WHEN 'Price' THEN str(Price, 16,4)
ELSE NULL
END ASC
realize however that any sorting on the price will now be done on character basis, i.e., 100 will come before 20
ORDER BY
CASE #OrderBy
WHEN 'ProductId' THEN ProductId
WHEN 'Name' THEN Name
WHEN 'Category' THEN Category
WHEN 'Price' THEN Price
ELSE NULL
END ASC
won't work when you are mixing numeric and non numeric branches. The type of the CASE expression is that of the branch with the highest datatype precedence. You can use
ORDER BY
CASE #OrderBy
WHEN 'ProductId' THEN ProductId
WHEN 'Name' THEN Name
WHEN 'Category' THEN Category
WHEN 'Price' THEN Price
ELSE CAST(NULL AS SQL_VARIANT)
END ASC
or
ORDER BY CASE
WHEN 'ProductId' = #OrderBy THEN ProductId
END,
CASE
WHEN 'Name' = #OrderBy THEN Name
END,
CASE
WHEN 'Category' = #OrderBy THEN Category
END,
CASE
WHEN 'Price' = #OrderBy THEN Price
END
Also you might get a better plan if you use OPTION (RECOMPILE) with this last one.
I think it is because of your CASE expression. First line of your case is WHEN 'ProductId' THEN ProductId and therefore it is converting every other field to the same datatype as ProductId. It works for Price as Price can be converted as INT but fails for Name as it encounters an error converting data type nvarchar to numeric.
Try this to get the same results:
CASE #OrderBy
WHEN 'ProductId' THEN RIGHT(100000000 + ProductId, 8)
WHEN 'Name' THEN Name
WHEN 'Category' THEN Category
WHEN 'Price' THEN RIGHT(100000000 + Price, 8)
ELSE NULL
END ASC
Right() function is used to get the same order for numeric and varchar values of your ProductId and Price columns. Please apply the same changes for Order by DESC section.

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)

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.