NULL IN (1,2,NULL) returns false - sql

Why does this SQL-statement return 0?
SELECT CASE WHEN NULL IN (9,1,NULL) THEN 1 ELSE 0 END

SQL is based on three-valued logic, where there are three truth values: TRUE, FALSE, and UNKNOWN. The special NULL value is a placeholder for "missing data" and if you compare something, anything, with missing data the result is unknown.
For example, is <missing data> equal to <missing data>? It's impossible to know, so the result is UNKNOWN.
In this particular case, you are trying to find out if <missing data> is in a given list: since the data is missing, it's impossible to know if it's in the list, and the query returns 0.
SELECT CASE WHEN NULL IN (9,1,NULL) THEN 1 ELSE 0 END

I don't know what RDBMS you are using since some of them has configuration on how NULL will be treated.
NULL IN (9, 1, NULL)
can be written as
(NULL = 9) OR (NULL = 1) OR (NULL = NULL)
and not of them were TRUE nor FALSE. They are all NULL. Since there are only two paths in the CASE statement, it falls under ELSE block.

It depends on how NULL is treated in the specific server.
For instance in SQL SERVER you may set ANSI_NULLS off and have it return 1:
SET ANSI_NULLS OFF
SELECT CASE WHEN NULL IN (9,1,NULL) THEN 1 ELSE 0 END
For further info you should read the remarks section of SET ANSI_NULLS

Because you can't compare null values using comparison operators. You can only use is null or is not null or using functions like COALESCE(), ISNULL(). For example
SELECT CASE WHEN COALESCE(NULL,-1) IN (9,1,-1) THEN 1 ELSE 0 END

Null mean not defined.
NUll object is not equal to other NULL
NULL==1 return false
NUll==2 return false
NULL==NULL return also false

Related

SQL Server Compare to NULL

I have a lot of comparisons that I need to make between a value and its previous value.
For Example: ReceivedBy and PreviousReceivedBy.
I started with:
WHERE ReceivedBy != PreviousReceivedBy
But if either value is null then this returns false, (when I really need it to be true). So I updated it to look like this:
WHERE ReceivedBy != PreviousReceivedBy
OR (ReceivedBy IS NULL AND PreviousReceivedBy IS NOT NULL)
OR (ReceivedBy IS NOT NULL AND PreviousReceivedBy IS NULL)
This works fine, but I have a large list of fields that need to be compared. I would like to find a way to make this comparison with less code (without turning off ANSI_NULLS).
Obviously if there is no other way, then I will just put in all 3 lines for the comparison.
UPDATE:
As an example, here is what I am hoping for
ReceivedBy = 123
PreviousReceivedBy = 123
Result = FALSE
ReceivedBy = 5
PreviousReceivedBy = 123
Result = TRUE
ReceivedBy = NULL
PreviousReceivedBy = 123
Result = TRUE
ReceivedBy = 123
PreviousReceivedBy = NULL
Result = TRUE
ReceivedBy = NULL
PreviousReceivedBy = NULL
Result = FALSE
If both columns are varchars, I'd go with something like this:
coalesce(ReceivedBy, 'NULL') != coalesce(PreviousReceivedBy, 'NULL')
If they are integers, I'd put some values greatly below zero (to distinctly represent null value) instead of 'NULL'.
From names of columns I assume it has to be wether string value or integer value :)
UPDATE
As #Siyual pointed out, replacement string should be "out of the realm of possibility", you should replace 'NULL' above with some non-alphabetical character, as '#' :)
I encountered the same problem with you when taking comparison with nullable value, NULL always returns unknown as far away of our desired only between TRUE or FALSE
I ended up with declare a Scalar-valued functions with these logics like other SQL(s) dealing with null as
Ordinary comparison operators yield null (signifying "unknown"), not
true or false, when either input is null. For example, 7 = NULL yields
null, as does 7 <> NULL. When this behavior is not suitable, use the
IS [ NOT ] DISTINCT FROM constructs:
a IS DISTINCT FROM b => a != b
a IS NOT DISTINCT FROM b => a == b
Which a IS NOT DISTINCT FROM b could be rewritten as
(a IS NOT NULL AND b IS NOT NULL AND a=b) OR (a IS NULL AND b is NULL)
I use sql_variant for these basic parameters: int, datetime, varchar,...
create function IsEqual(
#a sql_variant,
#b sql_variant
)
returns bit
as
begin
return (CASE WHEN (#a IS NOT NULL AND #b IS NOT NULL AND #a=#b) OR (#a IS NULL AND #b is NULL) THEN 1 ELSE 0 END);
end
create function IsNotEqual(
#a sql_variant,
#b sql_variant
)
returns bit
as
begin
return 1-dbo.IsEqual(#a,#b);
end
To use
select dbo.IsEqual(null, null) Null_IsEqual_Null,
dbo.IsEqual(null, 1) Null_IsEqual_1,
dbo.IsEqual(1, null) _1_IsEqual_Null,
dbo.IsEqual(1, 1) _1_IsEqual_1,
dbo.IsEqual(CAST('2017-08-25' AS datetime), null) Date_IsEqual_Null,
dbo.IsEqual(CAST('2017-08-25' AS datetime), CAST('2017-08-25' AS datetime)) Date_IsEqual_Date
For your cases
select dbo.IsNotEqual(123,123) _123_IsNotEqual_123,
dbo.IsNotEqual(5,123) _5_IsNotEqual_123,
dbo.IsNotEqual(Null,123) Null_IsNotEqual_123,
dbo.IsNotEqual(123,Null) _123_IsNotEqual_Null,
dbo.IsNotEqual(Null,Null) Null_IsNotEqual_Null
Another method without munging the data would be to use COALESCE
Where ReceivedBy != PreviousReceivedBy
And Coalesce(ReceivedBy, PreviousReceivedBy) Is Not Null
NULL cannot equal anything, not even another NULL, so if any of the values are NULL, ReceivedBy != PreviousReceivedBy will evaluate as true.
Secondly, if both of the values are NULL, the Coalesce(ReceivedBy, PreviousReceivedBy) Is Not Null will evaluate as false, forcing those to be filtered.
If neither are NULL, the first condition would fail if they are equal.
Admittedly, it’s not saving too much code, but it is an improvement.
This can be easily grouped in parenthesis and copy/pasta’d for all remaining fields you need to check.
Where (ReceivedBy != PreviousReceivedBy And Coalesce(ReceivedBy, PreviousReceivedBy) Is Not Null)
And[Or] (Foo != Bar And Coalesce(Foo, Bar) Is Not Null)
...
WHERE ISNULL(ReceivedBy, -1) != ISNULL(PreviousReceivedBy, -1)
assuming the columns never have negative values
if either value is null then this returns false
It actually returns "unknown" rather than "false" (SQL uses three valued logic). But in a WHERE clause the combined predicates must evaluate to "true" for the row to be returned so the effect here is much the same.
From SQL Server 2022 you can use
WHERE ReceivedBy IS DISTINCT FROM PreviousReceivedBy
(NULLIF(#a, #b) IS NOT NULL) OR (NULLIF(#b, #a) IS NOT NULL) means "#a != #b even if one of them or both are null.

Comparing 2 null datetimes not returning equal

In an update trigger I am comparing 2 nullable datetimes, that both happen to be null in my current test and it is returning that they are not equal.
#ExpirationDateChanged = case when i.ExpirationDate = d.ExpirationDate then 0 else 1 end
In this case i.ExpirationDate and d.Expiration date are null but instead of getting the expected 0, I am getting 1.
Can anyone explain this behavior?
NULL is not equal to NULL using equality comparison.
Comparing NULL values for equality is UNKNOWN under ANSI setting (the default)
You need to add an extra condition to explicitly test both columns for NULL using IS NULL:
#ExpirationDateChanged = case
when (i.ExpirationDate IS NULL AND d.ExpirationDate IS NULL) OR
i.ExpirationDate = d.ExpirationDate then 0
else 1
end

Dynamic where clause based on variable

I am working on a select statement in SQL and am running into issues trying to create a where clause that includes a case statement or an if else statement. I want to select records based on the value of a variable. If the variable is 'True' then only return records from the select statement where a column is null. If the variable is not 'True' then return all records regardless if that columns is null.
Any tips on how to do this?
Below is a simple example of what i am trying to do:
declare #option1 as varchar(5)
--This can be True or False so to test i just put the Set option below
set #option1 = 'True'
Select a,b,c,d...
from ...
where d = case when #option1 = 'True' then NULL End
This is the part where i do not know what to do. I only need to filter out the records if the variable is 'True' so not sure what to put in the else section of the case.
You can't test for d = NULL as your CASE statement does because that will always return false since NULL is not equal to NULL (unless you set ANSI_NULLS to 'off').
The simplest thing to do would be to change the WHERE clause to this:
WHERE #option1 = 'False' OR d IS NULL
If you prefer to use a CASE statement for some reason, you can write it like this:
WHERE 1 = CASE WHEN #option1 = 'False' THEN 1
WHEN #option1 = 'True' AND d IS NULL THEN 1
ELSE 0
END
This:
UPDATE: PinnyM has straightened me out on this. I am leaving my embarrassing logically flawed argument here for the education of the masses. The solution I propose below after "Try this" is certainly still valid, but PinnyM's solutions is by far more elegant and should be used.
WHERE #option1 = 'False' OR d IS NULL
Will always return all the results given his current select statement (assuming #Option1 is simply a flag parameter passed in).
Try this:
SELECT a, b, c, d
WHERE
-- Returns only rows where d is null (if #Option1 is True)
(#Option1 = 'True' AND d IS NULL)
OR
-- returns all the rows (if #Option1 is False)
(#Option1 = 'False')

Why are "Null Like X" and "Not (Null Like X)" equal?

I understand why these 2 statements are false
NULL LIKE 'X'
NULL NOT LIKE 'X'
However, what I don't understand is why these are :
NOT (NULL LIKE 'X')
NOT (NULL NOT LIKE 'X')
For example, these two statements should, I think, return different values :
SELECT CASE WHEN NOT (NULL LIKE 'X') THEN 'True' ELSE 'False' END
SELECT CASE WHEN (NULL LIKE 'X') THEN 'True' ELSE 'False' END
SQL uses a three-valued logic. You say that these are all false:
NULL LIKE 'X'
NULL NOT LIKE 'X'
NOT (NULL LIKE 'X')
NOT (NULL NOT LIKE 'X')
but that's actually not true. They're all null, which is neither true nor false.
A WHEN or WHERE clause rejects non-true values, which means null values as well as false ones, so it may seem like null is the same as false, but as you've noticed, it's not. :-)
NULL is always NULL you cannot compare it to something like that. It cannot be true or false which is what you are looking for as an answer.
It is just like you cannot compare, this will always return zero rows because NULL is an unknown value.
SELECT *
FROM yourTable
WHERE yourCol = NULL
NULL is always undefined.
So the value of
not (NULL like X) is undefined
and also
not (NULL not like X) is undefined
The best approximation of Undefined is NULL so both statements evaluate to NULL (NOT, you understand, equal NULL, we don't know what they equal)

Comparing a value to a NULL in t-SQL

I was curious if it's legal in t-SQL to compare a NULL to a value?
For instance, if I have:
WITH ctx AS(SELECT 123 AS n0, NULL AS n1)
SELECT n0 FROM ctx
WHERE ctx.n1 < 130
the WHERE clause in that case is always evaluated as FALSE. Is it something I can rely on?
You can't compare NULL with any other value, it will result in 'UNKNOWN'.
From msdn source
A value of NULL indicates that the value is unknown. A value of NULL
is different from an empty or zero value. No two null values are
equal. Comparisons between two null values, or between a NULL and any
other value, return unknown because the value of each NULL is unknown.
All boolean operations in T-Sql with null value returns 'UNKNOWN', which is recognized as false in clauses. You can use ISNULL function when you want set some default value.
for example in your case:
WITH ctx AS(SELECT 123 AS n0, NULL AS n1)
SELECT n0 FROM ctx
WHERE isnull(ctx.n1,0) < 130
It depends on the value of ANSI_NULLS.
http://msdn.microsoft.com/en-us/library/ms191270%28v=sql.90%29.aspx
When SET ANSI_NULLS is ON, a comparison in which one or more of the
expressions is NULL does not yield either TRUE or FALSE; it yields
UNKNOWN.
Transact-SQL supports an extension that allows for the comparison
operators to return TRUE or FALSE when comparing against null values.
This option is activated by setting ANSI_NULLS OFF. When ANSI_NULLS is
OFF, comparisons such as ColumnA = NULL return TRUE when ColumnA
contains a null value and FALSE when ColumnA contains some value
besides NULL.
The WHERE clause in the following = is also FALSE. You need to be very careful with NULLs
WITH ctx AS
(
SELECT 123 AS n0, NULL AS n1
)
SELECT *
FROM ctx
WHERE ctx.n1 = NULL
I've always used the EXISTS keyword along with EXCEPT like so
SELECT 1
WHERE EXISTS ((SELECT 1) EXCEPT (SELECT NULL))