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

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

Related

Unpredictable behaviour in nested CASE statement

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

XQuery Comparison Expressions in SQL Column

I would like to have a table that I can store XQuery Comparison Expressions in, so that I can evaluate them in a query.
I've been doing a bit of R&D into if it is possible, and I'm struggling.
If I put an XQuery expression in a column, then it seems to evaluate differently to if I put the XQuery expression directly into the query. For example, when I run the below query:
declare
#x xml = ''
create table #condition
(
condition nvarchar(255)
)
insert into #condition
values
('''1''=''1''')
select
condition,
#x.query('sql:column("condition")'),
#x.query('''1''=''1''')
from #condition
I would expect this to return:
'1'='1', true, true
However it actually returns:
'1'='1', '1'='1', true
Does anybody know how I can evaluate comparison expressions that are stored in a column?
The eventual plan is to be able to use this technique to filter down rows of a table based on XQuery conditions present. So ultimately I'd want to be able to do this in the where clause of a select statement.
I've put the above example into an sql fiddle encase it is useful.
Many thanks
Short answer: Unfortunately you can't.
sql:column("condition") will be evaluated to a suitable XML primitive data type based on the table column type. In this case the value from condition column will always be evaluated as XML string type instead of an XQuery statement, as you have figured out from running your sample query. And I can't see anyway of evaluating dynamic XQuery statement, unless you want to construct the entire query dynamically and execute it later on possibly using sp_executesql.
Try this query:
declare
#x xml = ''
create table #condition
(
condition nvarchar(255)
)
insert into #condition
values
('''1''=''1''')
select
condition,
case when col1 like col2 then 'True' else 'False' END col,
quer
from
(
select
condition,
PARSENAME(REPLACE(condition,'=','.'),2) col1,
PARSENAME(REPLACE(condition,'=','.'),1) col2 ,
#x.query('''1''=''1''') as quer
from #condition
)base

NULL comparison in SQL server 2008

I know that in SQL when we compare two NULL values, result is always false. Hence, statements like
SELECT case when NULL = NULL then '1' else '0' end
will always print '0'. My question is how functions like ISNULL determine whether value is null or not. Because, as per my understanding (and explained in above query) comparison of two null values is always FALSE.
You need to set the set ansi_nulls off and then check your result. Null can be thought of as an unknown value and when you are comparing two unknown values then you will get the result as false only. The comparisons null = null is undefined.
set ansi_nulls off
SELECT case when NULL = NULL then '1' else '0' end
Result:-
1
From MSDN
When SET ANSI_NULLS is OFF, the Equals (=) and Not Equal To (<>)
comparison operators do not follow the ISO standard. A SELECT
statement that uses WHERE column_name = NULL returns the rows that
have null values in column_name. A SELECT statement that uses WHERE
column_name <> NULL returns the rows that have nonnull values in the
column. Also, a SELECT statement that uses WHERE column_name <>
XYZ_value returns all rows that are not XYZ_value and that are not
NULL.
As correctly pointed by Damien in comments the behavior of NULL = NULL is unknown or undefined.
Your initial assumption appears to be that ISNULL is an alias for existing functionality which can be implemented directly within SQL statements, in the same way that a SQL function can. You are then asking how that function works.
This is an incorrect starting point, hence the confusion. Instead, like similar commands such as IN and LIKE, ISNULL is parsed and run within the database engine itself; its actual implementation is most likely written in C.
If you really want to look into the details of the implementation, you could take a look instead at mySQL - it's open source, so you may be able to search through the code to see how ISNULL is implemented there. They even provide a guided tour of the code if required.
... or {2} are you literally asking how the ISNULL function in SQL
Server itself works?
Actually I am asking for the second{2}. How ISNULL function in SQL server
works. If comparison of two nulls is not defined/unknown then how
isnull function compares two null values to return appropriate
results?
Null is a special marker used in Structured Query Language (SQL) to indicate that a data value does not exist in the database. ... NULL (SQL)
ISNULL ( check_expression , replacement_value ) is not concerned with comparison of values at all. It is concerned purely with the existence of value in the first parameter.
It tests if the check_expression has any value. If it does have any value that value is returned. If check_expression has no value the ISNULL function returns the second parameter replacement_value.
It does NOT compare the two values. It tests forthe existence of value in the first parameter only.
set ansi_nulls off
SELECT case when NULL = NULL then '1' else '0' end
result=1
set ansi_nulls on
SELECT case when NULL = NULL then '1' else '0' end
result=0
so that is the difference
I hope it works
SELECT CASE WHEN ISNULL(NULL,NULL) = NULL THEN 1 ELSE 0 END
SELECT case when 'NULL' = 'NULL' then '1' else '0' end
SELECT case when isnull(columnname,'NULL')='NULL' then '1' else '0' end
SET ANSI_NULLS OFF
SELECT case when NULL = NULL then '1' else '0' end

Select a column if other column is null

I need to select a field called ProgramID from a table and if the ProgramID is NULL then I need to select the value in the InterimProgramID from the same table and alias it as ProgramID.
How can I make a conditional SELECT statement to do this?
You need the ISNULL function.
SELECT ISNULL(a, b)
b gets selected if a is null.
Also, you can use the WHEN/THEN select option, lookup in BOL. Essentially: its c switch/case block meets SQL.
select COALESCE ( ProgramID , InterimProgramID ) as 'ProgramID'
You can use either the ISNULL function or the COALESCE function. They both do pretty much the same thing, however ISNULL only takes two parameters and COALESCE takes multiple parameters (returning the first non-null it encounters). Both try the first param, then the second, (and COALESCE continues on)
DECLARE #IAMNULL VARCHAR
DECLARE #IAMNOTNULL VARCHAR
SET #IAMNOTNULL = 'NOT NULL'
SELECT ISNULL(#IAMNULL, #IAMNOTNULL)
--Output: 'NOT NULL'
DECLARE #IAMNULLALSO VARCHAR
SELECT COALESCE(#IAMNULL, #IAMNULLALSO, #IAMNOTNULL)
--Output: 'NOT NULL'
SELECT ProgramID
FROM a_table
WHERE ProgramID IS NOT NULL
UNION
SELECT InterimProgramID AS ProgramID
FROM a_table
WHERE ProgramID IS NULL;
Coalesce('zzz-' + ProgramID, InterimID) as programID will still ignore ProgramID even if you have a pretext value. It's a cool little function
There is also:
Select NVL(Column_A, Column_B) From 'schema'.'table_name'
The NVL( ) function is available in Oracle, and not in MySQL or SQL Server. This function is used to replace NULL value with another value. It is similar to the IFNULL Function in MySQL and the ISNULL Function in SQL Server.
https://www.1keydata.com/sql/sql-nvl.html
You can also use IFNULL function
select IFNULL(ProgramId,interimId) as ProgramId

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?