Overcoming SQL's lack of short-circuit of AND conditions - sql-server

I'm processing an audit log, and want to ignore entries where a NULL value is changed to zero (or remains NULL). The Old and New values are held in NVARCHAR fields regardless of the type of the fields being logged. In order to CAST a new value to decimal, to determine if it's zero, I need to restrict to cases where ISNUMERIC of the field returns 1.
I've got it to work with this strange bit of SQL - but feel sure there must be a better way.
WHERE MessageDesc LIKE 'poitem%'
AND NOT(OldValue IS NULL AND 0.0 =
CASE
WHEN ISNUMERIC(NewValue) = 1 THEN CAST(NewValue AS DECIMAL(18,4))
WHEN NewValue IS NULL THEN 0.0
ELSE 1.0
END)
Any suggestions?

SQL Server 2012 added a Try_Convert function, which returns NULL if the value cannot be casted as the given type. http://technet.microsoft.com/en-us/library/hh230993.aspx
WHERE NOT (OldValue is Null AND
(NewValue is null OR try_convert(float, NewValue) = 0.0)
)
If using a version prior to 2012, check out Damien_The_Unbeliever's answer here: Check if a varchar is a number (TSQL) ...based on Aaron's comment this will not work in all cases.
Since you are using SQL 2008, then it appears a combination of isnumeric and a modified version of Damien's answer from the link above will work. Your current solution in your question would have problems with values like '.', '-', currency symbols ($, etc.), and scientific notation like '1e4'.
Try this for SQL 2008 (here is SQLFiddle with test cases: http://sqlfiddle.com/#!3/fc838/3 ): Note: this solution will not convert text values to numeric if the text has commas (ex: 1,000) or accounting notation with parens (ex: using "(1)" to represent "-1"), because SQL Server will throw an error when trying to cast to decimal.
WHERE t.OldValue is null
AND
(
t.NewValue is null
OR
0.0 =
case
when isnumeric(t.NewValue) = 1
--Contains only characters that are numeric, negative sign, or decimal place.
--This is b/c isnumeric will return true for currency symbols, scientific notation, or '.'
and not (t.NewValue like '%[^0-9.\-\+]%' escape '\')
--Not other single char values where isnumeric returns true.
and t.NewValue not in ( '.', '-', '+')
then cast(t.NewValue as decimal(18,4))
else null --can't convert to a decimal type
end
)

Avoid ISNUMERIC() since it is problematic with '.'.
-- Dot comes up as valid numeric
select
ISNUMERIC('.') as test1,
ISNUMERIC('1A') as test2,
ISNUMERIC('1') as test3,
ISNUMERIC('A1') as test4
-- Results window (text)
test1 test2 test3 test4
----------- ----------- ----------- -----------
1 0 1 0
Use COALESCE() instead.
WHERE MessageDesc LIKE 'poitem%'
AND
NOT (OldValue IS NULL AND
CAST(COALESCE(NewValue, '0') AS DECIMAL(18,4)) = 0)

Related

SQL String are same but case equals method returns false

I am using SQL to compare two columns and return TRUE/FALSE if they are equal.
In some cases, the two columns contain exactly the same string (no spaces or anything) but I am still getting false.
What may the reason for this be?
I am using this code:
CASE WHEN column1 = column2 THEN 0 ELSE 1 END AS [check]
The values are different despite the displayed value.
Using T-SQL, run a query like this to see the exact difference in the underlying raw values:
SELECT
column1
, CAST(column1 AS varbinary(MAX)) AS column1Binary
, column2
, CAST(column2 AS varbinary(MAX)) AS column2Binary
FROM dbo.YourTable;
This will reveal underlying differences like tabs or subtle character differences.
In fact, a likely explanation for what you are seeing is that one/both of the strings has leading and/or trailing whitespace. On SQL Server you may try:
CASE WHEN LTRIM(column1) = LTRIM(column2) THEN 0 ELSE 1 END AS [check]
If the above does not detect the problematical records, then try checking the length:
CASE WHEN LEN(column1) = LEN(column2) THEN 0 ELSE 1 END AS [check2]

'<number>D' causes CAST to error

I'm finding this to be truly bizarre behaviour.
Here is my T-SQL:
declare #testText nvarchar(1000);
set #testText = '17D4,A,1';
select txt_value from fn_ParseText2Table (#testText , ',' )
where fn_ParseText2Table is a function that parses the text into a table where you can get the txt, int, and floating point values, if they work.
The 17D4 is a product code that I'm trying to extract within a larger query, and all other 3817 records work fine.
select (
select txt_value
from fn_ParseText2Table(t.primaryKeyValues , ',' ) as pk
where position = 1) as product_NBR
from database.dbo.tablesToParse as t
where t.tableName = 'ProductData'
I found the function here.
What I've found is that if the string starts with some numbers (I've tested anywhere from 1-4 ) followed by 'D', it fails with the 'Error converting data type varchar to numeric.' message.
All other combinations of text work. Lower case d is fine. C is fine, E, F, etc. So '17F5,A,1' is fine. Also 'asdf 17D5,A,1' is fine. '1D,A,1' is not fine.
I'm very confused. Is there a special escape character in T-SQL for 'D'?
Update:
I should clarify that the error occurs inside fn_ParseText2Table()
Update 2
It's SQL server 10 - 64 bit, running on a windows 2008 server.
As well, I've tested this in a sql mgr query window:
declare #testText nvarchar(1000);
set #testText = '17D4';
select isnumeric( #testText )
The IsNumeric() call returns 1, which is why the fn_ParseText2Table() function tries to cast it to an in and it fails. I could add an extra check to that function to lower the text first and see if that's also numeric.
I figured it was related to floating-point literals but I was surprised it wouldn't cast to a numeric type. I guess it only works when float is the target type. You'll find the explanation here:
http://www.sqlservercentral.com/Forums/Topic202581-8-1.aspx
This behavior doesn't match up with other SQL Server literals, ie, constants:
select 1d -- literal 1 with d treated as the column alias
select 1e -- literal 1.0 as a float
select cast('1d' as float), cast('1e' as float) -- neither of these will cast without an exponent value
select cast('1d0' as float), cast('1e0' as float) -- these work though

Why is '-' equal to 0 (zero) in SQL?

When you run the following query in SQL Management studio the result will be 1.
SELECT
CASE WHEN '-' = 0 THEN
1
ELSE
0
END
That scares me a bit, because I have to check for 0 value a numerous number of times and it seems it is vulnerable for being equal to value '-'.
You're looking at it the wrong way around.
'-' is a string, so it will get implicitly casted to an integer value when comparing it with an integer:
select cast('-' as int) -- outputs 0
To make sure that you are actually comparing a value to the string '0', make your comparison like this instead:
select case when '-' = '0' then 1 else 0 end
In general, you're asking for trouble when you're comparing values of different data types, since implicit conversions happen behind the scene - so avoid it at all costs.

Converting text to Datetime in specific format

I need an expression which will convert a text (VARCHAR) column to a DATETIME if, and only if, it matches dd/MM/yyyy, d/MM/yyyy, dd/M/yyyy or d/M/yyyy. If it doesn't match then I want a NULL.
I have this...
CASE ISDATE([DateField])
WHEN 1 THEN CONVERT(DATETIME,[DateField],103)
ELSE NULL
END
However this fails for '15/04/76' for example - with a "Conversion failed when converting datetime from character string" error - whereas I would want it to return NULL
Example output
'1/6/1976' -> 1976-06-01
'01/06/1976' -> 1976-06-01
'13/06/2001' -> 2001-06-13
'06/13/2001' -> NULL
'13/06/76' -> NULL
Is there a way of forcing ISDATE to validate a given format?
The documentation seems to suggest so...
ISDATE is deterministic only if used with the CONVERT function, the
CONVERT style parameter is specified and style is not equal to 0, 100,
9, or 109.
But ISDATE only takes one argument, so how do I "use it with CONVERT function" if I am not doing so already?
You could do a nested case statement here. The first could check to see if you have a 10 character string 2 for day, 2 for month, 4 for year and 2 for separators = 10 characters.
SET DATEFORMAT DMY;
Case When DateField Like '%/%/[0-9][0-9][0-9][0-9]'
Then Case When IsDate(DateField) = 1
Then CONVERT(DATETIME,[DateField],103)
End
End
Revised: I changed the code to use a like search which forces there to be a /YYYY at the end of the string, and then does an IsDate check to allow for a single day and/or month.
Well, first off, why on earth are you storing datetime values in a varchar column? This is a cardinal sin for a variety of reasons, not the least of which is that you get no validation whatsoever that the data is (or is convertible to) a datetime. You should also consider validating the input, even if you leave the column as varchar, so you don't have such a wide variety of potential formats that you want to consider valid.
So here is one way, borrowing a bit from #G Mastros:
DECLARE #f TABLE(i INT, d VARCHAR(32));
INSERT #f VALUES
(1,'15/04/76'),
(2,'15/04/1976'),
(3,'1/3/1976'),
(4,'1/3/76'),
(5,'15/3/1976'),
(6,'22/22/22'),
(7,'Yesterday');
SET DATEFORMAT DMY;
SELECT i, d, d2 = CASE WHEN ISDATE(d) = 1
AND d LIKE '%/[0-9][0-9][0-9][0-9]'
THEN CONVERT(DATETIME, d, 103) END
FROM #f;
Results:
i d d2
- ---------- -----------------------
1 15/04/76 NULL
2 15/04/1976 1976-04-15 00:00:00.000
3 1/3/1976 1976-03-01 00:00:00.000
4 1/3/76 NULL
5 15/3/1976 1976-03-15 00:00:00.000
6 22/22/22 NULL
7 Yesterday NULL
PS this will be a great case for TRY_CONVERT in SQL Server 2012. It does exactly what you're asking - it tries to convert to the specified data type; if it can't, it returns NULL.
Thanks for the responses folks.
I've done it like this...
CASE ISDATE([DateField]) WHEN 1 THEN
CASE WHEN SUBSTRING([DateField],LEN([DateField])-4,1) = '/' THEN
CASE WHEN CHARINDEX('/',[DateField],LEN([DateField])-3)=0 THEN
CONVERT(datetime, [DateField] , 103)
END
END
END
which is pretty nasty business so would still appreciate something neater!
But this doesn't work either - it still errors on mm/dd/yyyy format dates!
Scrap that last comment - it does seem to work now? Probably something to do with SET DATEFORMAT

How to determine the field value which can not convert to (decimal, float,int) in SQL Server

I have a SQL Server database.
One field has values which are like
ID VALUE
1 NEGATIF
2 11.4
3 0.2
4 A RH(+)
5 -----
6 >>>>>
7 5.6<
8 -13.9
I want to CONVERT VALUE field to decimal, of course convert-able fields.
What kind of SQL statement can do this?
How can I understand which value is raising error while converting?
PS: I think this can solve WHERE VALUE LIKE '[a-z]' but how can I add more filter like [-+ ()] ?
Plain ISNUMERIC is rubbish
Empty string, +, - and . are all valid
So is +. etc
1e-3 is valid for float but not decimal (unless you CAST to float then to decimal)
For a particularly cryptic but failsafe solution, append e0 or .0e0 then use ISNUMERIC
SELECT
ISNUMERIC(MyCOl + 'e0') --decimal check,
ISNUMERIC(MyCOl + '.0e0') --integer check
So
SELECT
ID, VALUE,
CAST(
CASE WHEN ISNUMERIC(VALUE + 'e0') = 1 THEN VALUE ELSE NULL END
AS decimal(38, 10)
) AS ConvertedVALUE
FROM
Mytable

Resources