Handling blank query results, ISNULL(NULLIF()) - sql

Im trying to return the results of a query into a variable to I am able to use it in a insert statement. However my select keeps returning null even though I believe I have the ISNULL(NULLIF()) set up correctly.
declare #test varchar(10)
set #test = (select ISNULL(NULLIF(discnumber,''),'NA')
from discmaster
where meteredfilename = '3501' or nonmeteredfilename='3501')
select #test
It seems correct but still returns a NULL value. Would you Select Case but not 100% sure on that method either.

I'd move the ISNULL outside of the subquery - what if the subquery returns no results?
set #test = COALESCE(
(select NULLIF(discnumber,'')
from discmaster
where meteredfilename = '3501' or nonmeteredfilename='3501')
,'NA')
(I've also been a snob and replaced ISNULL with COALESCE. Outside of a few scenarios, COAELSCE is the better function).

Instead of ISNULL(NULLIF(discnumber,''),'NA')
try COALESCE(discnumber,'NA')

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

Why date field is getting saved as null?

Why #OpeningDate is getting saved as NULL even though I am doing this.
PROCEDURE [dbo].[InsertCaseANDHearingDetails]
#HearingDate datetime,
#IsOpeningDate bit= null,
#OpeningDate date= null,
AS
Begin
IF(#IsOpeningDate = 0)
Begin
Set #OpeningDate= (Select Convert(varchar, #HearingDate, 106))
End
Insert Into Hearings
values (#HearingDate, #OpeningDate)
End
Even though I am calculating it and hearing date is not null but why OpeningDate is getting saved as NULL.
#HearingDate != NULL will not work. The result of this comparison is always unknown. Use #HearingDate is not null instead.
Because the variable #HearingDate is not initialized, it would have a null value.
Also, the variable #OpeningDate wouldn't be set to Select Convert(varchar, #HearingDate, 106) because the if condition evaluates to unknown.
Hence, when you select values from the table they would be null.
Edit:
#IsOpeningDate bit= null
...
IF(#IsOpeningDate = 0)
This condition evaluates to unknown too as this is doing 0 = null. You cannot compare with null.
It will work if you use the following at the start of the Proc.
SET ANSI_NULLS OFF
Basically NULL can't be compared, not even with itself, because this is not a value. If at all, you want the engine to treat it as one, you need to set off the ANSI_NULLS property. That said, I would prefer to go with #vkp's answer any day.
Based on your edit, it looks like the below condition is not met:
IF(#IsOpeningDate = 0)
and hence, it remains NULL.
in the if condition why dont you check is null instead of comparing it to 0
Your query
IF(#IsOpeningDate = 0)
My suggestion
IF(#IsOpeningDate IS NULL)
Sorry it was my mistake to no mention parameters in Insert statement and since NULL were allowed so it inserted NULLs
Thanks for your help as it led me to figure out that what's actually going on.

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

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.

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