Safe convert from string to date in Transact - sql-server

I have the following code in a stored procedure in MSSQL Transact:
CAST(CONVERT(Datetime, aof.Transactiondate) AS date)
aof.Transactiondate is a varchar(8) and should be written in the form of '20160202' or '20160117'.
Today I found out that in rare instances aof.Transactiondate can be a '1' and thus my code crashes completely.
Can I do anything to make sure that the above don't crash and sets a defult date or something instead of crashing??
Theoretically I could make sure that there is 8 chars in the varchar and then seperate the char into 4, 2 and 2 block and finally make sure that block one is between 2015 and 2016 (only relevant at the moment), second block is between 1 and 12 and third block is between 1 and 31 but that seems to be a huge amount of work.

TRY_CAST
Returns a value cast to the specified data type if the cast succeeds; otherwise, returns null.

You can use the ISDATE() function to test for a valid date before converting the data:
DECLARE #TransactionDate VARCHAR(8)= 1;
SELECT CONVERT( DATE,
CASE
WHEN ISDATE(#TransactionDate) = 1
THEN #TransactionDate
ELSE NULL
END);
SET #TransactionDate = '20160202';
SELECT CONVERT( DATE,
CASE
WHEN ISDATE(#TransactionDate) = 1
THEN #TransactionDate
ELSE NULL
END);
Output:
2016-02-02

There are several functions available in T-SQL under the Conversion category, all of which have variants prefixed with TRY_ which:
Returns a value cast to the specified data type if the cast succeeds; otherwise, returns null.
CAST / TRY_CAST
CONVERT / TRY_CONVERT
PARSE / TRY_PARSE
You can use them like this:
SELECT TRY_CAST('2020-05-27' AS DATE)
SELECT TRY_CONVERT(DATE, '2020-05-27')
SELECT TRY_PARSE('2020-05-27' AS DATE)

Related

Different aggregate functions depending on datatype

I have a T-SQL script that returns all columns in a table, along with datatype and max value MAX(DATALENGTH)) fetching it from sys.columns and sys.types.
However the max value will always be 4 for ints, since ints uses 4 bytes. In this case I'd rather have the highest numeric value of the column.
I figured I might change my query to use DataLength for string-based columns, and a MAX() for number based columns, however I run into some problems before I even get there:
Minified example code
DECLARE #A bit = 1
SELECT CASE WHEN 1=1 THEN MAX(DATALENGTH(#A)) ELSE MAX(#A) END
I would expect to receive the number 1 given that 1=1 is true.
Instead I get an error
Operand data type bit is invalid for max operator.
I understand that you can't run MAX(#A) on a bit, but that's not what I'm trying to do. My goal is to run different aggregate functions depending on the datatype.
How can I solve this?
My goal is to run different aggregate functions depending on the datatype.
This will fail because you will get invalid cast errors or will get implicit conversions to the highest precedence data type
Your use of bit is irrelevant here
smalldatetime has the highest precedence so this code gives odd results when mixing datatypes
DECLARE #foo table (
intval int,
floatval float,
datetimeval smalldatetime)
INSERT #foo VALUES
(1, 1.567E2, '2017-07-31'),
(2, 2.0, '2017-08-01');
DECLARE #Switch int;
SELECT
CASE
WHEN #Switch=1 THEN MAX(intval)
WHEN #Switch=2 THEN MAX(floatval)
ELSE MAX(datetimeval)
END
FROM
#foo
SET #Switch = 1
1900-01-03 00:00:00
SET #Switch = 2
1900-06-06 16:48:00
SET #Switch = 3
2017-08-01 00:00:00
In this case, you are missing a cast :
SELECT CASE WHEN 1=1 THEN MAX(DATALENGTH(#A)) ELSE MAX(CAST(#A as bigint)) END

SQL Server Conversion error: Conversion failed when converting the nvarchar value 'XXX' to data type int

Running some pretty simple SQL here:
select *
from table
where columnA <> convert(int,columnB)
and isnumeric(columnB) = 1
Still getting this error every time:
Conversion failed when converting the nvarchar value 'XXX' to data type int.
If you're using SQL Server 2012 or more recent you could use TRY_PARSE which will return NULL when the parse fails.
SELECT TRY_PARSE('one' as int) -- NULL
, TRY_PARSE('1' as int) -- 1
, TRY_PARSE('0.1' as int) -- NULL
Returns the result of an expression, translated to the requested data type, or null if the cast fails in SQL Server. Use TRY_PARSE only for converting from string to date/time and number types.
Isnumeric has a lot of odd behavior. For example, it also considers currency signs such as $ or £, and even a hyphen (-) to be numeric.
I think you'd be better of using NOT columnB like '%[^0-9]%' to ONLY take numbers into account.
Check the comments at the bottom of the msdn page for isnumeric(), which you can find here: https://msdn.microsoft.com/en-us/library/ms186272.aspx
This may sound weird, but it breaks when do not put the ISNUMERIC check first. Try this out:
WITH [Table]
AS
(
SELECT columnA,columnB
FROM
(
VALUES (1,'2'),
(2,'XXX')
) A(columnA,columnB)
)
select *
from [Table]
where ISNUMERIC(columnB) = 1 --this works
AND columnA <> convert(int,columnB)
--where columnA <> convert(int,columnB) --this doesn't work
-- and isnumeric(columnB) = 1
I suggest you to reverse your checking like this:
SELECT *
FROM table
WHERE CONVERT(NVARCHAR, columnA) <> columnB
I got this using a combination of the answers and comments here. I used a CASE statement in my WHERE clause and also had to use LIKE instead of ISNUMERIC to account for illegal characters. I also had to use BIGINT because a few select samples were overflowing the INT column. Thanks for all of the suggestions everybody!
select * from patient
where PatientExternalID <>
(case when mrn not like '%[^0-9]%'
then convert(bigint, mrn)
else 0
end)

Conversion into a time format

there's a query where I get this error:
Conversion failed when converting date and/or time from character string.
Here's the query:
Select cast(RESULT_APPROVE_FULL_DATE as date) as RESULT_APPROVE_FULL_DATE, dtvl18, cast(RESULT_APPROVE_FULL_DATE as time) as RESULT_APPROVE_FULL_DATE_2, tdvl18
from #a1
left join #b1
on ADNR18=PATIENT_ID
and INST18=isuf_lab
and STNR18=request_number
and cast(RESULT_APPROVE_FULL_DATE as date)= cast(cast(dtvl18 as varchar) as date)
and cast(RESULT_APPROVE_FULL_DATE as time)=cast(cast(tdvl18 as varchar) as time)
The problem definitely lies in the last clause, since when I remove it, everything works )but I need that one). Namely, the problem is in conversion of tdvl18 (decimal(4,0), null) into a time format.
As it can be inferred, the tdvl18 field looks like this, for example: 947, 1525, 2359 etc. How can these decimal values be converted into a time format (hh:mm:ss.nnnnnnn)?
Thanks!
It is failing because SQL Server is failing to recognise string values as valid times. So your current errors can be reproduced with:
declare #someval as varchar(10) = '525'
select cast(#someval as time)
-- Conversion failed when converting date and/or time from character string.
If you format the values with a : in the correct place (before the last 2 digits) using the STUFF method, then the conversion should work for your values:
declare #someval as varchar(10) = '525'
select cast(stuff(#someval, len(#someval) - 1,0, ':') as time)
-- 05:25:00.0000000
This seems to work directly on decimal values too so you can avoid casting to varchar first:
declare #someval as decimal(4,0) = 525
select cast(stuff(#someval, len(#someval) - 1,0, ':') as time)
-- 05:25:00.0000000
Changing your last clause to this might work, assuming RESULT_APPROVE_FULL_DATE is casting to a time value correctly:
and cast(RESULT_APPROVE_FULL_DATE as time)
= cast(stuff(tdvl18, len(tdvl18) - 1,0, ':') as time)

SQL Server - Cast invalid value to int

Is there any way to deal with SQL casts if the input data is corrupt?
Let's say I have a column of datatype NVarchar(10) and want to cast this column to int.
Let's also say that some of the nvarchar values are corrupt, so they can't be converted to int.
Is there any way to silently ignore these, default them to 0 or some such?
DECLARE #t TABLE (Numbers VARCHAR(20))
INSERT INTO #t
VALUES
('30a'),('30'),('100'),
('100a'),('200'),('200a')
SELECT CASE
WHEN ISNUMERIC(Numbers) = 1
THEN CAST(Numbers AS INT) ELSE NULL END AS Number
FROM #t
ISNUMERIC Function returns 1 when it is an integer value you can use this function.
Result
Number
NULL
30
100
NULL
200
NULL
it will cast the integer values to INT and ignore the values that cannot be cast to Int
Try this with PatIndex() function:
select id, val
from t
where patindex('%[^0-9]%',val) = 0
Note: above query is filtering out corrupted values, if you need to bring them in with 0 values, please use a case expression as below.
select id, case when patindex('%[^0-9]%',val) = 0
then convert(int, val)
else 0 end val
from t
Fiddle demo for both queries
I'll be the unpopular one and advise REGEX because ISNUMERIC, while sometimes useful, doesn't catch everything. This answer on SO excellently covers some REGEX concepts, for instance:
One numeric digit
Probably the easiest one of the bunch:
WHERE Column LIKE '[0-9]'
For more details, here's a useful REGEX workbench by Phil Factor and Robyn Pae.

Getting conversion failed error even when CONVERT function is not being called SQL

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

Resources