I'm having an issue with the below query:
SELECT
Consignments.LegacyID,
Consignments.TripDate,
Consignments.CollectionName,
CASE
WHEN Sage2.InvoiceSummaryType = 'HT' THEN DeliveryTown
ELSE DeliveryName + ', ' + DeliveryTown + ', ' + DeliveryPostCode END AS 'DeliveryName',
Consignments.Pallets,
Consignments.Weight,
Consignments.BaseRate,
Consignments.FuelSurcharge,
Consignments.AdditionalCharges,
Consignments.BaseRate * Consignments.Quantity AS 'InvoiceValue',
Consignments.InvoiceNumber,
Consignments.Customer
FROM
Consignments
INNER JOIN SageAccount
ON Consignments.Customer = SageAccount.LegacyID
AND SageAccount.Customer = 'true'
LEFT OUTER JOIN SageAccount AS Sage2
ON SageAccount.InvoiceAccount = Sage2.LegacyID
WHERE
(Sage2.Customer = 'true')
AND (Consignments.Customer = #Customer)
AND (Consignments.InvoiceNumber IS NOT NULL)
OR (Sage2.Customer = 'true')
AND (Consignments.InvoiceNumber IS NOT NULL)
AND (Sage2.InvoiceAccount = #Customer)
ORDER BY
CASE
WHEN Sage2.InvoiceSummaryType = 'HR' THEN TripDate
WHEN Sage2.InvoiceSummaryType = 'HS' THEN Consignments.LegacyID
END
For some reason, it keeps giving me the following error:
The conversion of a char data type to a datetime data type resulted in an out-of-range datetime value order by
But only when it tries to Order By TripDate, i.e. when the case 'HR' happens. TripDate is a 'datetime field'.
Any ideas?
Having just read the question again I can't explain the specific symptons you are getting without seeing the execution plan (I would have expected HS to cause the problem). Generally though you should avoid mixing datatypes in CASE expressions as below as it simply doesn't work select case when 1=0 then GETDATE() else 'foo' end will fail as it tries to convert the string to datetime
ORDER BY
CASE
WHEN Sage2.InvoiceSummaryType = 'HR'
THEN TripDate
WHEN Sage2.InvoiceSummaryType = 'HS'
THEN Consignments.LegacyID
END
To get around this you can use cast(TripDate as float) - assuming (perhaps incorrectly) that the ID field is numeric or use this idiom.
ORDER BY
CASE
WHEN Sage2.InvoiceSummaryType = 'HR'
THEN TripDate
ELSE NULL
END,
CASE
WHEN Sage2.InvoiceSummaryType = 'HS'
THEN Consignments.LegacyID
ELSE NULL
END
You would need to check the execution plans for performance comparisons.
Don't you need an "END" after your THEN TripDate ?
All the options in the CASE statement have to be the same datatype. Is LegacyID a char?
You'd have this problem anywhere, not just in an order by. If you do CASE WHEN 'x' THEN (some int) WHEN 'y' THEN (some date) and SQL cannot do an implicit conversion of all values, then you're toast.
If you're doing a cased ordering, then the data types have to be compatible with each other.
try this for the date:
Convert(int, TripDate , 112)
A format of 112 will give you yyyymmdd, which is useful in ordering by date as an integer. Use 1 - [date] for descending.
This is assuming that the LegacyID is an integer. Otherwise, you can try casting the date to a type SQL_VARIANT
Related
I am very curious, not sure if I missed something here.
But using Cast (yyMMdd) in a case statement throws an error
'Conversion failed when converting date and/or time from character string.'
But in a single select like below, it returns the result
select case
when ISDATE('000000') = 0 then 'False'
else CAST('950705' as date)
end [YEAR]--error on else
select CAST('950705' as date) [Year]-- 1995-07-05 (works)
The error is not on the else but on the then 'false' - due to datatype precedence and that case can only return a single datatype. you're trying to resturn a date and a string.
use try_convert or try_cast
select try_cast('000000' as date)
,try_cast('950705' as date) as [YEAR]
There is one if case where I am using IF #SKU IS NULL OR #SKU = '', but my friend says it will take more time as compare to IF ISNULL(#SKU, '') = ''. So you should use IF ISNULL(#SKU, '') = ''. But I think I'm using correct. So please suggest me which one is run faster.
This is my stored procedure:
CREATE PROCEDURE USP_GetExistingRefunds
(
#OrderNo VARCHAR(50),
#SKU VARCHAR(255),
#ProfileID INT
)
AS
BEGIN
--IF ISNULL(#SKU, '') = '' --this work faster or
IF #SKU IS NULL OR #SKU = '' --this work faster
BEGIN
SELECT OrderNo, SKU, ISNULL(Quantity, 0) Quantity, ISNULL(Amount, 0) Amount
FROM StoreRefundOrder SRO
INNER JOIN StoreRefundOrderItem SROI ON SRO.ID = SROI.RefundOrderID
WHERE SRO.OrderNo = #OrderNo
AND ProfileID = #ProfileID
END
ELSE
BEGIN
SELECT OrderNo, SKU, ISNULL(SUM(Quantity), 0) Quantity, ISNULL(SUM(Amount), 0) Amount
FROM StoreRefundOrder SRO
INNER JOIN StoreRefundOrderItem SROI ON SRO.ID = SROI.RefundOrderID
WHERE SRO.OrderNo = #OrderNo
AND SROI.SKU = #SKU
AND ProfileID = #ProfileID
GROUP BY OrderNo, SKU
END
END
In the context of an IF/ELSE Procedural batch
It doesn't make any difference whatsoever. It literally takes 0.00 MS to determine if a value is blank or unknown, it takes the 0.00MS to determine if ISNULL(#SKU, '') = ''. If there is a difference it would likely be measured in nanoseconds IMO. Again, this in the context of a procedural batch because the statement is only being evaluated once.
In the context of an FILTER (e.g. ON, WHERE or HAVING CLAUSE)
Here the difference is actually enormous, it cannot be understated. This is tricky to explain with parameters and variables involved, so, for brevity, I show you an example with this sample data:
IF OBJECT_ID('tempdb..#things','U') IS NOT NULL DROP TABLE #things;
SELECT TOP (10000) Txt = SUBSTRING(LEFT(NEWID(),36),1,ABS(CHECKSUM(NEWID())%x.N))
INTO #things
FROM (VALUES(1),(30),(40),(NULL)) AS x(N)
CROSS JOIN sys. all_columns;
UPDATE #things SET Txt = NEWID() WHERE txt = '0';
CREATE NONCLUSTERED INDEX nc_things1 ON #things(Txt);
The following queries will find rows that either do or do not contain blanks or nulls
-- Finding things that are blank or NULL
SELECT t.Txt
FROM #things AS t
WHERE t.Txt IS NULL OR t.Txt = '';
-- Finding things that are NOT blank or NULL
SELECT t.Txt
FROM #things AS t
WHERE NOT(t.Txt IS NULL OR t.Txt = '');
SELECT t.Txt
FROM #things AS t
WHERE t.Txt > '';
-- Finding things that are blank or NULL
SELECT t.Txt
FROM #things AS t
WHERE ISNULL(t.Txt,'') = '';
-- Finding things that are NOT blank or NULL
SELECT t.Txt
FROM #things AS t
WHERE ISNULL(t.Txt,'') <> '';
The first three queries are SARGable, the last two are not because of ISNULL. Even though there's an index to help me, the ISNULL renders it useless here. It's the difference between asking someone to look in a phone book for everyone whose name begins with "A" and finding everyone who's name ends with "A".
SARGable predicates allow a query to seek a portion of an index where non-SARGable predicates force a query to scan the entire table REGARDLESS of the how many matching rows exist (if any). When you are dealing with millions/billions of rows joined to many other tables the difference can be a query that runs in seconds to one that, in some cases, may run for hours or even weeks (I've seen a few).
EXECUTION PLANS:
Note that this last one WHERE t.Txt > '' will work too. Any non-null text value is > '' and if t.Txt was NULL then it will also evaluate to false. I include this because this expression works for filtered indexes. The only catch is you can't use it on a text field where Implicit conversion can transform this into the number 0 or less. Note these queries:
IF '' = 0 PRINT 'True' ELSE PRINT 'False'; -- Returns True
IF '' = '0' PRINT 'True' ELSE PRINT 'False'; -- Returns False
IF '' > -1 PRINT 'True' ELSE PRINT 'False'; -- Returns True
IF '' > '-1' PRINT 'True' ELSE PRINT 'False'; -- Returns False
IF #SKU IS NULL OR #SKU ='' is checking null and blank both. In second case if Isnull(#sku,'') you are checking null and assigning '' for null value. Both are different cases.
Which is faster (ISNULL(#SKU, '') = '') or (#SKU IS NULL OR #SKU =
'')
It really doesn't matter in this case. If you were comparing against a column then (SKU IS NULL OR SKU = '') would be preferable as it can use an index but any difference for a single comparison against a variable will be in the order of microseconds and dwarfed by the execution times of the SELECT statements.
To simplify the IF statement I'd probably invert it anyway as below
IF #SKU <> '' --Not null or empty string
BEGIN
SELECT OrderNo, SKU, ISNULL(SUM(Quantity), 0) Quantity, ISNULL(SUM(Amount), 0) Amount
FROM StoreRefundOrder SRO
INNER JOIN StoreRefundOrderItem SROI ON SRO.ID = SROI.RefundOrderID
WHERE SRO.OrderNo = #OrderNo
AND SROI.SKU = #SKU
AND ProfileID = #ProfileID
GROUP BY OrderNo, SKU
END
ELSE
BEGIN
SELECT OrderNo, SKU, ISNULL(Quantity, 0) Quantity, ISNULL(Amount, 0) Amount
FROM StoreRefundOrder SRO
INNER JOIN StoreRefundOrderItem SROI ON SRO.ID = SROI.RefundOrderID
WHERE SRO.OrderNo = #OrderNo
AND ProfileID = #ProfileID
END
A little long for a comment.
As has been noted by many already, in this case your two options don't really make any appreciable difference. But in the future, when you think of a couple of different ways to code something, there are standard practices you can implement quickly and easily to test for yourself.
At the top of your code block, add this command:
SET STATISTICS TIME, IO ON;
You can use either TIME or IO, but I almost always want to see both, so I always turn them both on at the same time.
The output from this addition will show up in your Messages window after your query or queries run and will give you tangible information about which method is faster, or causes less stress on the SQL Server engine.
You'll want to run a few tests with each option. Warm cache / cold cache especially, but a few iterations is best one way or the other to get an average or eliminate outlier results.
I'm weird, so I always close my code block with:
SET STATISTICS TIME, IO OFF;
But strictly speaking that's unnecessary. I just have a thing about resetting anything I change, just to avoid any possibility of forgetting to reset something that will matter.
Kendra Little has an informative blog post on using STATISTICS.
I have this sql
DECLARE #CurrentLocation geography;
SET #CurrentLocation = geography::Point(#Latitude,#Longitude,4326)
SELECT
ID,
RestaurantTitle,
CuisineName,
Latitude, Longitude,
MinimumOrder,
ImagePath,
CASE
WHEN r.OpeningTimeType IN ('24*7')
THEN r.OpeningTimeType
ELSE r.Breakfast_OpenTime
END AS OpenTime,
CASE
WHEN r.Breakfast_OpenTime > CONVERT (time, CURRENT_TIMESTAMP)
AND R.Dinner_CloseTime < CONVERT (time, CURRENT_TIMESTAMP)
THEN 'PRE ORDER'
ELSE 'ORDER NOW'
END AS BUTTONNAME,
ROUND(ROUND(GeoLocation.STDistance(#CurrentLocation), 0) / 1609.34, 0) AS Distance
FROM
[dbo].[tbl_Restaurant1] r
CROSS APPLY
(SELECT
CuisineName + ','
FROM
tbl_Cuisine
WHERE
tbl_Cuisine.ID IN (SELECT CuisineID_FK
FROM tbl_Restaurant_Cuisine
WHERE r.ID = RID_FK)
FOR XML PATH('')) D (CuisineName)
WHERE
#CurrentLocation.STDistance([GeoLocation]) <= 40000
When I ran this SQL, an exception is thrown:
Conversion failed when converting date and/or time from character string
Here are the columns:
ID int
RestaurantTitle varchar(50)
Latitude decimal(10, 6)
Longitude decimal(10, 6)
MinimumOrder int
ImagePath varchar(50)
OpeningTimeType varchar(50)
breakfast_OpenTime time
A CASE in T-SQL is an expression that returns exactly one atomic value. Therefore, all different options of the CASE must return the same datatype (or at least compatible ones) - and this is not so in your CASE here:
CASE
WHEN r.OpeningTimeType IN ('24*7')
THEN r.OpeningTimeType
ELSE r.Breakfast_OpenTime
END AS OpenTime,
OpeningTimeType is varchar(50), while Breakfast_OpenTime is of type time. Therefore, SQL Server needs to convert one value into the common return type.
Based on the SQL Server type precedence, it will try to convert both possible return values into TIME and that's where it fails.
So in order to fix this, it's up to you to make sure the CASE variations all return the same datatype so that no implicit conversions need to take place. Since you obviously cannot guarantee that OpeningTimeType can be converted to a TIME, you need to do the opposite - convert the Breakfast_OpenTime to a VARCHAR(50):
CASE
WHEN r.OpeningTimeType IN ('24*7')
THEN r.OpeningTimeType
ELSE CAST(r.Breakfast_OpenTime AS VARCHAR(50))
END AS OpenTime,
I have a query in SSMS which is returning 1900-01-01, how can I use a CASE WHEN accurately to replace 1900-01-01 with '' (a blank not a null).
CAST(ISNULL(CAST(CONVERT(DATE, cmmt.[CmmtExpirationDate], 101) AS NVARCHAR(20)), '') AS DATE) AS [Cmmt Expiration Date]
Result: 1900-01-01
I tried this but no luck (terrible syntax):
CASE
WHEN (CAST(ISNULL(cast(convert(Date, cmmt.[CmmtExpirationDate] , 101) as nvarchar(20)), '') = '1900-01-01')
THEN ''
ELSE CAST(ISNULL(cast(convert(Date, cmmt.[CmmtExpirationDate] , 101) as nvarchar(20)),'') AS DATE
END
The result of your expression needs to have a fixed data type. DATE is not possible, since '' is not a valid date. nvarchar(20) would be an option, but that means that your result will be a string even if it is not 1900-01-01.
Once you accept that, the solution is simple:
CASE WHEN cmmt.[CmmtExpirationDate] = '1900-01-01'
THEN CONVERT(nvarchar(20), cmmt.[CmmtExpirationDate])
ELSE CONVERT(nvarchar(20), '')
END
You might want to specify the desired output format as a third parameter to the first CONVERT statement.
(I assume that CmmtExpirationDate is of type DATE, because if it isn't, it should have been mentioned in the question.)
You can use try_convert as below:
try_convert(date, case when datecolumn='' then null else datecolumn end)
I use this command to select all the specific dates if the given variable is date, if it is not it should return all of the fields.
The commands works when #query is in the form of a date, but it returns an error:
"Conversion failed when converting date and/or time from character string."
when it is any other arbitrary string.
Code:
select * from table where
format(dateofbirth,'dd/MMM/yyyy') = Case
when ISDATE(#query)=1 then
format(CONVERT(datetime,#query),'dd/MMM/yyyy')
else
format(dateofbirth,'dd/MMM/yyyy')
Edit:
#query can be any string for eg. "1/1/2013" , "random" , "3".
The command should return all fields if #query is not in form of a date.
You can work around this problem by re-formulating your query condition like this:
declare #query as varchar(20)='blah'
SELECT *
FROM testtable
WHERE ISDATE(#query) = 0
OR CONVERT(date, dateofbirth) = CASE ISDATE(#query)
WHEN 1 THEN CONVERT(date, #query) ELSE NULL
END
Demo on sqlfiddle.
The problem is that logic operators are not short-circuited in SQL, so the optimizer treats CONVERT(date, #query) as something that it can pre-compute to speed up the query. By expanding the condition to a CASE that depends entirely on #query you can eliminate the execution of the CONVERT branch when ISDATE(#query) returns "false".