This question already has answers here:
Why does NULL = NULL evaluate to false in SQL server
(21 answers)
Closed 6 years ago.
Problem:
I have a table with 2 columns , one char(1) and another varchar(1).
I insert a row with NULLS
I compare the 2 columns values
I get that they are not equal
Why?
Code:
CREATE TABLE ss_1
(
[char] CHAR(1) ,
[varchar] VARCHAR(3)
)
INSERT INTO TimeCurrent..ss_1
( char, varchar )
VALUES ( NULL,-- char - char(1)
NULL -- varchar - varchar(3)
)
SELECT CASE WHEN S.char = S.varchar THEN 'yes'
ELSE 'no'
END AS eq2
FROM ss_1 AS S
FIDDLE:
http://sqlfiddle.com/#!6/84bc4/2/0
Actually this is because SQL is based on three valued logic where a predicate can be evaluated to 3 different values: TRUE, FALSE and UNKNOWN. When any part of the comparison is NULL the predicate evaluates to UNKNOWN. This is the formal definition. Example:
1 = 1 => TRUE
1 = 2 => FALSE
1 = NULL => UNKNOWN
NULL = NULL => UNKNOWN
Now when you write:
CASE WHEN predicate THEN
It will go to THEN only when predicate evaluates to TRUE. But in your case it evaluates to UNKNOWN. Thats why it goes to ELSE part.
CREATE TABLE ss_1
(
[char] CHAR(1) ,
[varchar] VARCHAR(3)
)
INSERT INTO TimeCurrent..ss_1
( char, varchar )
VALUES ( NULL,-- char - char(1)
NULL -- varchar - varchar(3)
)
SELECT CASE WHEN coalesce(S.char, '') = coalesce(S.varchar, '') THEN 'yes'
ELSE 'no'
END AS eq2
FROM ss_1 AS S
Something like that might work for what you're after. As was mentioned, a direct comparison of null against another null value will never return as equal, as null simply means the absence of a value, so there's no value for the comparison to compare against. The input types have no bearing on this (this would be the same if both were char(1) or char(3), or some other data type for that matter).
EDIT: One thing to note, you'll want to replace the '' with some other (otherwise not legal) value if blank is a valid input in your database for that column, else you'll end up matching blank values in the database against null values, which may or may not be okay...
EDIT 2: This actually might be slightly better, and more reflective of your actual intentions:
CREATE TABLE ss_1
(
[char] CHAR(1) ,
[varchar] VARCHAR(3)
)
INSERT INTO TimeCurrent..ss_1
( char, varchar )
VALUES ( NULL,-- char - char(1)
NULL -- varchar - varchar(3)
)
SELECT CASE WHEN S.char = S.varchar THEN 'yes'
CASE When S.char IS NULL and S.varchar IS NULL Then 'yes'
ELSE 'no'
END AS eq2
FROM ss_1 AS S
Related
Been a while since I been coding in T-SQL, but I have an IF statement not working in a function. It must be a logic issue on my end but I can't see it.
If a piece of data, #pTownship is not blank and null than I am testing it to see if it is one, two, or three characters in length and returning the township number to the calling stored procedure. Following is my function code.
The issue is that when a piece of data, say 05N, is passed as #pTownship, the outer IF is not true the else is being executed so my internal IF conditions are never being executed. Since #pTownship is 05N it's NOT '' or NULL so why isn't my true condition being executed?
Thanks in advance for a second set of eyes.
CREATE FUNCTION core.fnTownshipNumber (#pTownship VARCHAR(50))
RETURNS INT
AS
BEGIN
DECLARE #TownshipNumber INT,
#InputLength INT;
IF #pTownship <> '' AND #pTownship <> NULL
BEGIN
SET #InputLength = LEN(#pTownship);
-- single character, based on the data, single character is always number so return it
IF #InputLength = 1
SET #TownshipNumber = CONVERT(INT, #pTownship);
-- double character, based on the data, double char are always number so return it
IF #InputLength = 2
SET #TownshipNumber = CONVERT(INT, #pTownship);
-- triple character, based on the data, third char is always direction so return first two
IF #InputLength = 3
SET #TownshipNumber = CONVERT(INT, SUBSTRING(#pTownship, 1, 2));
END;
ELSE
BEGIN
SET #TownshipNumber = NULL;
END;
RETURN #TownshipNumber;
END
The <> operator is not meant to compare to NULL. When you need to check for NULL, use
#pTownship IS NOT NULL
or if you prefer,
NOT (#pTownship IS NULL)
Related: Not equal <> != operator on NULL
In your case, you are comparing against both the empty string and null, a more concise way to capture both cases would be this:
IF NULLIF(#pTownship, '') IS NOT NULL BEGIN
/* ... */
END
You could omit the outer IF entirely, if you used this alternative:
SET #InputLength = LEN(COALESCE(#pTownship, ''));
The COALESCE function returns its second argument (the empty string in the example) when the first argument is NULL.
I think you might need to use the following form:
IF (#pTownship IS NOT NULL) AND (LEN(#pTownship) > 0)
I am not as certain about the second term; variations might also work. Parentheses could perhaps be omitted, but I might prefer to keep them.
On this, see How do check if a parameter is empty or null in Sql Server stored procedure in IF statement?
The following shows another way of implementing the logic with a case expression (not statement).
The first case implements the logic in a clear manner, though the initial when clause isn't really needed due to the else as a catch-all clause. The second variation takes advantage of Left() (or Substring()) tolerating a length greater than the input: it simply returns the entire string without error.
-- Sample data.
declare #Townships as Table ( Township VarChar(16) );
insert into #Townships ( Township ) values
( NULL ), ( '1' ), ( '23' ), ( '45N' ), ( 'Upper Lemming' );
-- Demonstrate conversion.
select Township,
case
when Township is NULL or Township = '' then NULL -- This can be left to the else clause.
when Len( Township ) in ( 1, 2 ) then Cast( Township as Int )
when Len( Township ) = 3 then Cast( Left( Township, 2 ) as Int )
else NULL end as TownshipNumber,
case
-- Since Left() doesn't mind being asked for more characters than are available ...
when Len( Township ) between 1 and 3 then Cast( Left( Township, 2 ) as Int )
else NULL end as TownshipNumberX
from #Townships;
I came across a bug where I was using CAST(Col1 AS INT) + CAST(Col2 AS INT) where both Col1 and Col2 are VARCHAR and I was getting valid results out when Col1 or Col2 was blank and I didn't expect this. I checked and CAST (and CONVERT) both have this default behavior of replacing blank with 0:
SELECT CAST('' AS INT)
SELECT CONVERT(INT, '')
I checked the info page and I can't see any reference to explain why this is the behavior (or change it through a server setting). I can of course work around this but I wanted to ask why this is the behavior as I do not think it is intuitive.
I'd actually rather this CAST failed or gave NULL, is there a server setting somewhere which effects this?
Consider an INT in SQL Server. It can be one of three values:
NULL
0
Not 0
So if you're casting/converting an empty string, which you are assuming is a number, then 0 is the most logical value. It allows for a distinction between NULL and 0.
SELECT CAST(NULL AS INT) -- NULL
SELECT CAST('' AS INT) -- 0
SELECT CAST('42' AS INT) -- 42
I'd say that's logical.
If you did:
SELECT CAST('abc' AS INT)
You'd get:
Conversion failed when converting the varchar value 'abc' to data type int.
If you do wish to handle empty strings as NULL use NULLIF as Bogdan suggests in his answer:
DECLARE #val VARCHAR(2) = ''
SELECT CAST(NULLIF(#val,'') AS INT) -- produces NULL
NULLIF returns the first expression if the two expressions are not equal. If the expressions are equal, NULLIF returns a null value of the type of the first expression.
Finally, if your columns are storing INT values, then consider changing its data type to INT if you can.
As you probably know NULL is a marker that indicates that a data value does not exist. And '' is a value, empty but value.
So MS SQL cast (or converts) empty value into 0 by default. To overcome this and show it as NULL you can use NULLIF
Simple example:
SELECT int_as_varchars as actual,
cast(NULLIF(int_as_varchars,'') as int) as with_nullif,
cast(int_as_varchars as int) as just_cast
FROM (VALUES
('1'),
(''),
(NULL),
('0')
) as t(int_as_varchars)
Output:
actual with_nullif just_cast
1 1 1
NULL 0
NULL NULL NULL
0 0 0
As you see NULLIF in that case will help you to get NULL instead of 0.
What about this ?
declare #t table(bucket bigint);
INSERT INTO #t VALUES (1);
INSERT INTO #t VALUES (2);
INSERT INTO #t VALUES (-1);
INSERT INTO #t VALUES (5);
INSERT INTO #t VALUES (0);
declare #Bucket bigint = 0 --filter by 0
select * from #t
where 1=1
AND ((#Bucket is Null or cast(#Bucket as nvarchar) = '') or bucket=#Bucket)
I recently ran into this pickle with the CASE-THEN-ELSE statement in SQL Server (2014 if it matters), to be more accurate, "the Simple" vs "the Searched" CASE expression. Until now, I thought that the only difference between these 2 is simply the format and/or the habit in writing both ways of the case expression but I guess I was completely wrong :)
MSDN Link
The CASE expression has two formats:
The simple CASE expression
compares an expression to a set of simple expressions to determine the
result.
The searched CASE expression evaluates a set of Boolean
expressions to determine the result.
Here is the example:
set nocount on
declare #test nvarchar(50) = null
select
#test as [The NULL Value],
case
when #test is null
then null
else 'Not Null???'
end as [As Expected],
case #test
when null
then null
else 'Not Null???'
end as [The Pickle]
And the result is:
The NULL Value As Expected The Pickle
-------------------------------------------------- ----------- -----------
NULL NULL Not Null???
Could someone provide a link to a MSDN documentation where this is explained, perhaps in a more detailed manner? :)
P.S.: I bet a lot of you folks were certain that both results would yield the same output :D
It's not weird at all ...
The "shortcut" way of
case #test
when null
then null
else 'Not Null???'
end as [The Pickle]
evaluates the variable/column (here: #test) against the values in the WHEN clauses (when null) with the regular equality operator - and comparing NULL using the standard equality operator (#test = null) is always undefined/NULL itself (standard SQL behavior), so it's not true
Therefore you are getting this result - Not Null??? - for your column The Pickle
If you want to check for NULL, you must use IS NULL like in your first example...
declare #t int =1
--simple case
select
case #t
when 1 then 1 else null end
The above query is expanded to below form..
select
case when #t=1 then 1 else null end
so a query with null will expand to below
declare #t int=null
select case #t
when null then null else 'notnull' end
is expanded to
select case when #t=null then null else 'notnull' end
which obviously evaluates to not null..
So in summary only in null case you will not get results you are expecting,try below to see
declare #t int=null
declare #t1 int =1
select
case when #t is null then null else 'notnull' end as 'Searchedcase',
case #t when null then null else 'notnull' end as'simple case',
case when #t1 =1 then 1 else null end as 'Searchedcase for value',
case #t1 when 1 then 1 else null end as'simple case for value'
See discussion of NULL and UNKNOWN from Transact-SQL reference to get a handle on why '=' doesn't work for NULL.
Simple CASE must be implicitly using '=', rather than IS NULL. So to make IS NULL explicit, use a Searched CASE expression.
Maybe Microsoft will add some functionality to the simple CASE expression to if NULL is encountered, then operator 'IS' is used?
I had recently posted a question about how to handle NULL values in SQL Server Reporting Services. The question is located at Handle NULL values in SQL Server Reporting Services.
Applying WHERE #MyParam=MyCol OR #MyParam IS NULL faces a problem, which is if I select single NON-NULL value (from the dropdownlist and pass this as in SQL Server), this also brings the NULL value records. It should only bring the Non NULL value's records.
What should be done to avoid NULL records, if NON-NULL records are selected?
What you are looking for is an exclusive OR (XOR):
-- Exclusive OR is an either/or combination which,
-- using => as "is defined as" symbol, can be shown as:
--
-- x XOR y => (x AND (NOT y)) OR ((NOT x) AND y)
-- x => #MyParam = MyCol
-- y => #MyParam IS NULL
-- DECLARE #MyParam NVARCHAR(32) = 'MC1,MC2'; -- returns two lines
-- DECLARE #MyParam NVARCHAR(32) = 'MC1'; -- returns one line
DECLARE #MyParam NVARCHAR(32) = NULL; -- returns one line
DECLARE #MyParamXML XML = N'<root><r>' + replace(#MyParam,',','</r><r>') + '</r></root>'
;WITH SOExample AS
(
SELECT cast('MC1' AS NVARCHAR(12)) AS MyCol, 'Line 1' AS MyCol2 UNION ALL
SELECT 'MC2' , 'Line 2' UNION ALL
SELECT NULL , 'Line NULL'
)
SELECT * from SOExample
WHERE
(#MyParam IS NULL AND ISNULL(MyCol,'<NULL Value>') = '<NULL Value>')
OR (#MyParam IS NOT NULL AND
SOExample.MyCol IN
(SELECT SSRSlist.MyParams.value('.','NVARCHAR(32)') as MyParam
FROM #MyParamXML.nodes('//root/r') as SSRSlist(MyParams)))
In a trigger in my sql-server 2008 database I need to check if exactly one variable is not null. This code does what I need, but can it be done in fewer lines and more readable?
DECLARE #string varchar
DECLARE #float float
DECLARE #bit bit
DECLARE #int int
Set #string=NULL -- Exactly one of these variables needs to be set
Set #float=NULL --
Set #bit=NULL --
Set #int=NULL --
IF( (#string is not null AND COALESCE(#float, #bit, #int) IS NULL)
OR (#float is not null AND COALESCE(#string, #bit, #int) IS NULL)
OR (#bit is not null AND COALESCE(#string, #float, #int) IS NULL)
OR (#int is not null AND COALESCE(#string, #float, #bit) IS NULL)
)
print ' ok'
ELSE
print ' not ok'
SELECT CASE WHEN COUNT(c) =1 THEN 'Y' ELSE 'N' END
FROM
(VALUES (CAST(#string AS SQL_VARIANT)),(#float),(#bit),(#int)) T (c)
I'm not necessarily sure it's any more readable (although I guess if you abstracted it to a function it might be) but
if((case when #string is null then 0 else 1 end +
case when #float is null then 0 else 1 end +
case when #bit is null then 0 else 1 end +
case when #int is null then 0 else 1 end) = 1)
....
is a little more flexible?
I thought I was being clever, but this will only work if none of the 4 varibles can have the exact same value, so it's not useful in many cases. I'll post it anyway:
IF(
(COALESCE(#string,#float, #bit, #int) IS NULL)
OR (COALESCE(#string, #float, #bit, #int) != COALESCE(#int, #bit, #float, #string))
)
print 'not ok'
ELSE print 'ok'
COALESCE evals from left to right until it hits a NOT NULL value, so if you invert the order you'll get a different result if you have more than one var. set (unless the varibles can have the same value at the same time)
I think I know the type of setup you're creating. In those circumstances, I usually define me data structures as:
CREATE TABLE DataItems (
DataItemID int IDENTITY(1,1) not null,
Name varchar(10) not null,
TypeRequired varchar(6) not null,
constraint PK_DataItems PRIMARY KEY (DataItemID),
constraint CK_TypeRequired CHECK (TypeRequired in ('STRING','FLOAT','BIT','INT'),
constraint UQ_DataItems_TypeCheck UNIQUE (DataItemID,TypeRequired)
)
note that I've made DataItemID,TypeRequired a superkey, so I can reference it in a foreign key constraint.
Now, in the table that's collecting the data:
CREATE TABLE Answers (
AnswerID int IDENTITY(1,1) not null,
/* Other columns to FK to e.g. Client, Users, Session, whatever */
DataItemID int not null,
Type varchar(6) not null,
StringValue varchar(max) null,
FloatValue float null,
BitValue bit null,
IntValue int null,
constraint PK_Answers PRIMARY KEY (AnswerID),
constraint FK_Answers_DataItems FOREIGN KEY (DataItemID) references DataItems (DataItemID),
constraint FK_Answers_DataItems_TypeCheck FOREIGN KEY (DataItemID,Type) references DataItems (DataItemID,TypeRequired),
constraint CK_Answers_TypeCheck CHECK (
(FloatValue is null or TypeRequired = 'FLOAT') and
(StringValue is null or TypeRequired = 'STRING') and
(BitValue is null or TypeRequired = 'BIT') and
(IntValue is null or TypeRequired = 'INT')),
constraint CK_Answers_NotNUll CHECK (
FloatValue is not null or StringValue is not null or BitValue is not null or IntValue is not null)
)
The second foreign key constraint ensures the type column matches the defined type for the data item, and the pair of check constraints ensure that exactly one column (and the right column) is not null.
If you need to hide the Type column from the users, then I'd suggest renaming the above table (e.g. _Answers) creating a view with an insert trigger:
CREATE VIEW Answers
WITH SCHEMABINDING
AS
SELECT
AnswerID,
DataItemID,
StringValue,
FloatValue,
BitValue,
IntValue
FROM
dbo._Answers
CREATE TRIGGER T_Answers_I
ON Answers
INSTEAD OF INSERT
AS
INSERT INTO _Answers (DataItemID,Type,StringValue,FloatValue,BitValue,IntValue)
SELECT i.DataItemID,di.Type,i.StringValue,i.FloatValue,i.BitValue,i.IntValue
FROM inserted i inner join DataItems di on i.DataItemID = di.DataItemID
I found an alternative solution but it isn't any fewer lines. It uses the bitwise XOR operator. I'm not sure if I like it or not - but it does mean that each variable only gets checked once rather than on each line so potentially it satisfies your readability requirement:
DECLARE #string varchar
DECLARE #float float
DECLARE #bit bit
DECLARE #int int
SET #string=NULL -- Exactly one of these variables needs to be set
SET #float=NULL --
SET #bit=NULL --
SET #int=NULL --
if ((case when #string is null then 1 else 0 end)
^ (case when #float is null then 1 else 0 end)
^ (case when #bit is null then 1 else 0 end)
^ (case when #int is null then 1 else 0 end)) = 1
print 'ok'
else
print 'not ok'
Comments? Criticisms? Also not sure how efficient the CASE statements are.
Can't we check by this:
IF ISNULL(#string, '') <> ''
OR ISNULL(#float, 0) <> 0
OR #bit IS NOT NULL
OR ISNULL(#int, 0) <> 0
PRINT 'There is atleast one value'
ELSE
PRINT 'ALL ARE NULL'