views:

90

answers:

6

Consider the following Transact sql.

DECLARE @table TABLE(val VARCHAR(255) NULL)

INSERT INTO @table (val) VALUES('a')
INSERT INTO @table (val) VALUES('b')
INSERT INTO @table (val) VALUES('c')
INSERT INTO @table (val) VALUES('d')
INSERT INTO @table (val) VALUES(NULL)

select val 
from @table
where val not in ('a') 

I would expect this to return

b, c, d, NULL

but instead it returns

b, c, d

Why is this the case? Is NULL not evaluated? Is NULL somehow in the set 'a'?

+3  A: 

to get the null row try:

select val 
from @table
where val not in ('a') OR val IS NULL

try these:

select 1 where null in (null)
select 1 where null in (1)
select 1 where 1 in (null)

all return zero rows

null=null is always false and null=anythng is always false, when testing the NULL row the WHERE clause is WHERE NULL in ('a'), so it is always false.

KM
I understand that, but I am wondering why I have to state that, NULL is not in the set 'a' so I would expect that row to return.
Matthew Vines
Null is undefined, it cannot be determined whether or not it is in a set because it cannot be compared to anything. Unless you change the way SQL Server handles nulls as suggested by Quesi. Otherwise you must check whether a value is undefined (i.e. is null).
Mayo
Matthew, NULL *never compares to anything*, not even itself. Because of that, you have to explicitly test for it using [NOT] IS NULL. Null isn't included because it can not be tested for inclusion "in ('a')".
DaveE
Thanks for the input. I am understanding how it works, but so many languages treat null as 'not a value' instead of 'unknown value' that the behavior surprises me.
Matthew Vines
+2  A: 

Null can't be directly compared to anything.

try

select val  
from @table 
where coalesce(val, '') not in ('a')  
HLGEM
+2  A: 

Try this and you will get a different answer:

set ansi_nulls off;
DECLARE @table TABLE(val VARCHAR(255) NULL)

INSERT INTO @table (val) VALUES('a')
INSERT INTO @table (val) VALUES('b')
INSERT INTO @table (val) VALUES('c')
INSERT INTO @table (val) VALUES('d')
INSERT INTO @table (val) VALUES(NULL)

select val 
from @table
where val not in ('a')

You have ansi_nulls on and so it treats nulls accordingly.

Quesi
For more information google "mathematical set operations" and start reading up on set logic. Set logic is good background knowledge for working with an RDMS.
Russell Steen
+2  A: 

You can't do equality with NULL in this way.

Consider this

select case when 'a' = NULL then 1 else 0 end
select case when 'a' <> NULL then 1 else 0 end

For above you'll get

result
------
  0
  0

For your query try

 where isnull(val,'') not in ('a') 
CResults
+3  A: 

The Answer

By default, NULL, to SQL, means "unknown". So, any comparison to "unknown" is "unknown".

For example, if a stranger is standing next to Bob, and you want to ask the question, "Is the stranger's name the same as Bob's?". The answer is unknown.

Since you're asking, "Return any records where the value is not 'A'". When it gets to that NULL column, SQL still says, "I don't know". Since the expression doesn't evaluate to true, that record isn't returned.

The Solution

Any expression that compares NULL to a constant will always be NULL, so use the special IS NULL and IS NOT NULL to test for NULL values.

This will get you b, c, d, and NULL:

SELECT val 
FROM @table
WHERE val NOT IN ('a') OR val IS NULL

The Workaround

You can change the default behavior for NULLs:

SET ANSI_NULLS OFF
Marcus Adams
Thank you for the stranger comment. I always looked at null as unset. So if I asked is that strangers name the same as Bob's the answer would be no, because the stranger has no name. Which is obviously not the same as expressed by set theory.
Matthew Vines
One caveat about the workaround: if you depend on this behavior, and your code is run "in the wild" (where you don't control the ANSI_NULLS setting), you may have seemingly random errors. Better *in that case* to test explicitly.
DaveE
@DaveE, I agree. I don't recommend the workaround. The solution is better.
Marcus Adams