Unpredictable behaviour in nested CASE statement - sql

I'm unable to figure out why the control goes always to a statement irrespective of inside CASE condition.
A normal SQL statement works, but with my table it does not work.
--Not working--
SELECT
CASE WHEN [INTERNALDESCRIPTION] IS NOT NULL THEN --INTERNALDESCRIPTION IS A TEXT FIELD
CASE WHEN 'INT' = 'INT' THEN -- Or 'TEXT' = 'INT'
REPLACE( CONVERT(VARCHAR(MAX),[INTERNALDESCRIPTION] ) ,'''','') --should have come here
ELSE
REPLACE( CONVERT(INT,[INTERNALDESCRIPTION] ) ,'''','' ) -- Always comes here no matter what condition
END
ELSE
'NULL'
END
FROM DBO.RESOURCESTRINGMASTER WITH(NOLOCK) WHERE 1=1
-------working--
DECLARE #VALUE1 AS varchar(max) = '1Test', #VALUE2 AS VARCHAR(MAX) = '2'
SELECT
CASE WHEN #VALUE1 IS NOT NULL THEN
CASE WHEN 'INT' = 'INT' THEN
REPLACE( CONVERT(VARCHAR(MAX),#VALUE1 ) ,'''','')
ELSE
REPLACE( CONVERT(INT,#VALUE2 ) ,'''','' )
END
ELSE
'NULL'
END
And results in below error:
Explicit conversion from data type text to int is not allowed.

Explicit conversion from data type text to int is not allowed.
This error message seems pretty clear. Why are you using a text data type? It is deprecated. To quote from the documentation:
IMPORTANT! ntext, text, and image data types will be removed in a future version of SQL Server. Avoid using these data types in new development work, and plan to modify applications that currently use them. Use nvarchar(max), varchar(max), and varbinary(max) instead.
So, your code on the real table is executing the ELSE condition, which causes it to fail. In the code with constants, ELSE condition is not failing. Why is this?
I think the error is being caught in the compilation phase of the query. The error does not occur in the second example, because SQL Server is short-circuiting the query, recognizing that the ELSE is not needed. The code in the second example is simply not compiled.
I am pretty sure you would see the same behavior if you replaced the code with 1 / 0 (although the other part of the case expression would need to change as well for the types to be compatible).

It is not running the statement, it is failing because it sees something illegal.
If you want to see it on the other case change this line
DECLARE #VALUE1 AS varchar(max) = '1Test', #VALUE2 AS VARCHAR(MAX) = '2'
to
DECLARE #VALUE1 AS TEXT = '1Test', #VALUE2 AS VARCHAR(MAX) = '2'
You are comparing different test cases -- the first is not using VARCHAR

Related

SQL HASHBYTES function returns weird output when used in CASE WHEN/IIF

I have written a stored procedure that hashes the value of a certain column. I need to use this HASHBYTES function in a CASE WHEN or IIF statement, like this:
DECLARE #Hash varchar(255) = 'testvalue'
SELECT IIF(1=1, HASHBYTES('SHA1',#Hash), #Hash)
SELECT CASE WHEN 1=1 THEN HASHBYTES('SHA1',#Hash) END AS Hashcolumn
I can't get my head around why I get different outputs from above queries? it seems that whenever I add an ELSE in the CASE WHEN / IIF statement, it returns a string of weird characters (like ü<þ+OUL'RDOk{­\Ìø in above example).
Can anyone tell me why this is happening? I need to use the CASE WHEN or IIF.
Thanks guys
IIF returns the data type with the highest precedence from the types in true_value and false_value. In this case, it's #Hash1 which is varchar(255) so your result is getting cast to varchar(255). See below.
DECLARE #Hash varchar(255) = 'testvalue'
SELECT cast(HASHBYTES('SHA1',#Hash) as varchar(255))
Similarly, CASE works the same way. However, if you don't add an ELSE or another WHEN that would conflict with the data type, it will work. This is because an ELSE NULL is implied. i.e.
SELECT CASE WHEN 1=1 THEN HASHBYTES('SHA1',#Hash) END
However, if you add another check, then precedence kicks in, and it will be converted.
SELECT CASE WHEN 1=1 THEN HASHBYTES('SHA1',#Hash) WHEN 1=2 THEN #Hash END AS Hashcolumn
SELECT CASE WHEN 1=1 THEN HASHBYTES('SHA1',#Hash) ELSE #Hash END AS Hashcolumn
The output of a select query is a virtual table. In a relational db a column of a table is constrained to single data type.. so here what happens is implicit conversion is being done by the server engine inorder to render a sigle type and hence weird characters are returned.
The nature of conversion is as #scsimon says it follows highest precedence order.
The following query should help.
DECLARE #Hash varchar(255) = 'testvalue'
SELECT IIF(1=1, CONVERT(VARCHAR(255),HASHBYTES('SHA1',#Hash),2), #Hash)
SELECT CASE WHEN 1=2 THEN CONVERT(VARCHAR(255),HASHBYTES('SHA1',#Hash),2)
ELSE #Hash END AS Hashcolumn

Difficulty printing one particular query in MSSQL

I'm trying to construct a small query which will pull data from individual fields in a DB and print them in a human readable list format (it's what the operators are used to seeing). The code I have here is far from complete but It seems to me that it should work.
DECLARE #PSUCARD VARCHAR(20)
DECLARE #EQUIPMENT VARCHAR(50)
DECLARE #T1 VARCHAR
SET #PSUCARD = 'PSU-888'
SET #EQUIPMENT = '123_POUCH'
PRINT #PSUCARD + ':'
PRINT #EQUIPMENT
PRINT ''
IF (SELECT TEMPERATURE_MAIN FROM PSU WHERE PSU.PART_ID = #PSUCARD AND PSU.OPERATION_RESOURCE_ID = #EQUIPMENT)IS NOT NULL BEGIN
SET #T1 = (SELECT TEMPERATURE_MAIN FROM PSU WHERE PSU.PART_ID = #PSUCARD AND PSU.OPERATION_RESOURCE_ID = #EQUIPMENT)
PRINT 'Temperature: ' + #T1
--(SELECT TEMPERATURE_MAIN FROM PSU WHERE PSU.PART_ID = #PSUCARD AND PSU.OPERATION_RESOURCE_ID = #EQUIPMENT)
END
If I execute the code as is, #T1 returns a * rather than a value. If I remove comments from the line below I am reassured that there is indeed a value there.
I have other code very similar to this which works fine. Any ideas?
Also, I don't know if this helps in diagnosing the problem, but despite the temperature field in the DB being an INT, I get a conversion message if I try to treat #T1 an an INT.
This is because you declared #T1 as VARCHAR without a length. According to this:
When n is not specified in a data definition or variable declaration
statement, the default length is 1. When n is not specified when using
the CAST and CONVERT functions, the default length is 30.
You should always specify a length when declaring a VARCHAR variable:
DECLARE #T1 VARCHAR(50)
You need to give length for varchar datatype else it is going to take only one character
DECLARE #T1 VARCHAR(50)

SQL Server NULL Integer to Empty String using ISNULL

My curiosity always gets the best of me and I've searched online for an explanation to this and came up with nothing (could be because I didn't use the right terms.)
Can someone please explain why SQL Server returns a value of zero (0) when the following is executed, instead of an empty string ('').
DECLARE #I AS INT
SET #I = NULL
SELECT ISNULL(#I, '') -- 0
As declared here, the second argument to ISNULL is the replacement_value, which "must be of a type that is implicitly convertible to the type of check_expresssion." Implicitly converting '' to INT results in 0.
Because #I is declared as an INT, the empty string is implicitly CAST as an integer resulting in a ZERO.
I know is an old question, but I want to share.
The issue here is not because the casting of the ' ' expression, is because the int data type is not null-able and, when you try to SET #I = NULL SQL set the default value zero(0).
That's why the statement ISNULL(#I, '') does not find any null in #I and returns its value(0).
Hope it helps somebody out there.

SQL Server 2008 inconsistent results

I just released some code into production that is randomly causing errors. I already fixed the problem by totally changing the way I was doing the query. However, it still bothers me that I don't know what was causing the problem in the first place so was wondering if someone might know the answer. I have the following query inside of a stored procedure. I'm not looking for comments about that's not a good practice to make queries with nested function calls and things like that :-). Just really want to find out why it doesn't work consistently. Randomly the function in the query will return a non-numeric value and cause an error on the join. However, if I immediately rerun the query it works fine.
SELECT cscsf.cloud_server_current_software_firewall_id,
dbo.fn_GetCustomerFriendlyFromRuleName(cscsf.rule_name, np.policy_name) as rule_name,
cscsf.rule_action,
cscsf.rule_direction,
cscsf.source_address,
cscsf.source_mask,
cscsf.destination_address,
cscsf.destination_mask,
cscsf.protocol,
cscsf.port_or_port_range,
cscsf.created_date_utc,
cscsf.created_by
FROM CLOUD_SERVER_CURRENT_SOFTWARE_FIREWALL cscsf
LEFT JOIN CLOUD_SERVER cs
ON cscsf.cloud_server_id = cs.cloud_server_id
LEFT JOIN CLOUD_ACCOUNT cla
ON cs.cloud_account_id = cla.cloud_account_id
LEFT JOIN CONFIGURATION co
ON cla.configuration_id = co.configuration_id
LEFT JOIN DEDICATED_ACCOUNT da
ON co.dedicated_account_id = da.dedicated_account_id
LEFT JOIN CORE_ACCOUNT ca
ON da.core_account_number = ca.core_account_id
LEFT JOIN NETWORK_POLICY np
ON np.network_policy_id = (select dbo.fn_GetIDFromRuleName(cscsf.rule_name))
WHERE cs.cloud_server_id = #cloud_server_id
AND cs.current_software_firewall_confg_guid = cscsf.config_guid
AND ca.core_account_id IS NOT NULL
ORDER BY cscsf.rule_direction, cscsf.cloud_server_current_software_firewall_id
if you notice the join
ON np.network_policy_id = (select dbo.fn_GetIDFromRuleName(cscsf.rule_name))
calls a function.
Here is that function:
ALTER FUNCTION [dbo].[fn_GetIDFromRuleName]
(
#rule_name varchar(100)
)
RETURNS varchar(12)
AS
BEGIN
DECLARE #value varchar(12)
SET #value = dbo.fn_SplitGetNthRow(#rule_name, '-', 2)
SET #value = dbo.fn_SplitGetNthRow(#value, '_', 2)
SET #value = dbo.fn_SplitGetNthRow(#value, '-', 1)
RETURN #value
END
Which then calls this function:
ALTER FUNCTION [dbo].[fn_SplitGetNthRow]
(
#sInputList varchar(MAX),
#sDelimiter varchar(10) = ',',
#sRowNumber int = 1
)
RETURNS varchar(MAX)
AS
BEGIN
DECLARE #value varchar(MAX)
SELECT #value = data_split.item
FROM
(
SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 1)) as row_num FROM dbo.fn_Split(#sInputList, #sDelimiter)
) AS data_split
WHERE
data_split.row_num = #sRowNumber
IF #value IS NULL
SET #value = ''
RETURN #value
END
which finally calls this function:
ALTER FUNCTION [dbo].[fn_Split] (
#sInputList VARCHAR(MAX),
#sDelimiter VARCHAR(10) = ','
) RETURNS #List TABLE (item VARCHAR(MAX))
BEGIN
DECLARE #sItem VARCHAR(MAX)
WHILE CHARINDEX(#sDelimiter,#sInputList,0) <> 0
BEGIN
SELECT #sItem=RTRIM(LTRIM(SUBSTRING(#sInputList,1,CHARINDEX(#sDelimiter,#sInputList,0)-1))), #sInputList=RTRIM(LTRIM(SUBSTRING(#sInputList,CHARINDEX(#sDelimiter,#sInputList,0)+LEN(#sDelimiter),LEN(#sInputList))))
IF LEN(#sItem) > 0
INSERT INTO #List SELECT #sItem
END
IF LEN(#sInputList) > 0
INSERT INTO #List SELECT #sInputList -- Put the last item in
RETURN
END
The reason it is "randomly" returning different things has to do with how SQL Server optimizes queries, and where they get short-circuited.
One way to fix the problem is the change the return value of fn_GetIDFromRuleName:
return (case when isnumeric(#value) then #value end)
Or, change the join condition:
on np.network_policy_id = (select case when isnumeric(dbo.fn_GetIDFromRuleName(cscsf.rule_name)) = 1)
then dbo.fn_GetIDFromRuleName(cscsf.rule_name) end)
The underlying problem is order of evaluation. The reason the "case" statement fixes the problem is because it checks for a numeric value before it converts and SQL Server guarantees the order of evaluation in a case statement. As a note, you could still have problems with converting numbers like "6e07" or "1.23" which are numeric, but not integers.
Why does it work sometimes? Well, clearly the query execution plan is changing, either statically or dynamically. The failing case is probably on a row that is excluded by the WHERE condition. Why does it try to do the conversion? The question is where the conversion happens.
WHere the conversion happens depends on the query plan. This may, in turn, depend on when the table cscf in question is read. If it is already in member, then it might be read and attempted to be converted as a first step in the query. Then you would get the error. In another scenario, the another table might be filtererd, and the rows removed before they are converted.
In any case, my advice is:
NEVER have implicit conversion in queries.
Use the case statement for explicit conversions.
Do not rely on WHERE clauses to filter data to make conversions work. Use the case statement.

SQL Server, Select CASE with different casting

I want to do a select that do a cast only for a specific ID but it doesn't seems to work.
Example :
SELECT
CASE
WHEN(#ID <> 1) THEN Code
WHEN(#ID = 1) THEN Cast(Code AS int)
END Code FROM ....
Any Idea ?
Why do want to do this? A SQL Server expression has a single fixed type. In other words, a single expression can't be varchar(50) or int depending on how the expression is evaluated. You could cast each case to sql_variant, but that may or may not make sense based on what you're trying to do.
EDIT
If you are executing this query from a stored procedure, you could create an IF..ELSE block to execute a different version of the query based on the value of #ID. For example:
IF (#ID = 1) BEGIN
SELECT Cast(Code AS int) AS Code FROM ...
END
ELSE BEGIN
SELECT Code FROM ...
END
It works for me. Check if the #id is of type int and if all values of column Code can be casted to int.
UPDATE If you have a value that can't be casted to int, your query won't work.
So you can write 2 different queries.
Smth like
IF #id = 1 THEN
SELECT code ...
ELSE
SELECT Cast(Code AS int) as Code
You could have also written:
select case when #ID = 1 then CAST(Code as int) else Code end as Code
from...
By the way, any data containing alphabetic characters won't cast to int.
Perhaps could we better help you if you tell us what you want to achieve, with some sample data provided?