Welcome to EMC Consulting Blogs Sign in | Join | Help

Claypole's World - The SQL Server Side

Concerning development of bespoke applications and database administration.

ISNULL() <> COALESCE(). Discuss.

Crikey.  That title sounds like an essay I had to write when I was at school and that was a long time ago...

Jamie Thomson raised an important point in a comment on a blog I posted yesterday entitled "Getting rid of getdate()".  In it I stated that ISNULL() and COALESCE() are in the same boat as getdate() and CURRENT_TIMESTAMP.  Both COALESCE() and CURRENT_TIMESTAMP are ANSI standard functions whereas their corollary T-SQL specific functions, ISNULL() and GETDATE() are not.  However, Jamie pointed out that ISNULL() does have a behaviour difference to COALESCE().  That set me thinking as to what if any other differences there were between these two functions.  Guess what I found one!

In Books On-line it states a key difference between the two functions.  When you COALESCE() an expression the value coalesced is Nullable.  Whereas if you used ISNULL then the value is NOT NULL.  This shows up when using these functions as computed columns.  I have to say this isn't something I do a lot but ignorance is no excuse in the eyes of the law and so I am not going to start making excuses now (although...I think I just have)...

So to take the Books On-line example:

CREATE TABLE #CheckSumTest 
(
        ID int identity ,
        Num int DEFAULT ( RAND() * 100 ) ,
        RowCheckSum AS COALESCE( CHECKSUM( id , num ) , 0 )  PRIMARY KEY
)

Produces an error:

Msg 1711, Level 16, State 1, Line 1
Cannot define PRIMARY KEY constraint on column 'RowCheckSum' in table '#CheckSumTest'. The computed column has to be persisted and not nullable.
Msg 1750, Level 16, State 0, Line 1
Could not create constraint. See previous errors.

Whereas this (note now using ISNULL()) does not:

CREATE TABLE #CheckSumTest 
(
        ID int identity ,
        Num int DEFAULT ( RAND() * 100 ) ,
        RowCheckSum AS ISNULL( CHECKSUM( id , num ) , 0 )  PRIMARY KEY
)

So there we have it a difference.  This is not the end of the story though.  It is still possible to use COALESCE() in this scenario.  You need to use the PERSISTED key word. Like this:

CREATE TABLE #CheckSumTest
(
        ID int identity ,
        Num int DEFAULT ( RAND() * 100 ) ,
        RowCheckSum AS COALESCE( CHECKSUM( id , num ) , 0 ) PERSISTED PRIMARY KEY
)

So functionally it can be the same.  Note the clue in the error message about persistence.  Functionally the same result can be achieved with either function and we can still safely dispose of ISNULL(). Hoorah.

This however brings me nicely onto Jamie's comment.  He pointed me at an article that shows an example of ISNULL() and COALESCE() producing different results.

The example was:

DECLARE @value VARCHAR(2)
DECLARE @result1 VARCHAR(4)
DECLARE @result2 VARCHAR(4)

SELECT @result1 = ISNULL(@value,'test')
SELECT @result2 = COALESCE(@value,'test')

SELECT @result1 AS Result
UNION ALL 
SELECT @result2 AS Result

With the result being:

Result

Yuck. That to me has all the hallmarks of being a bug! Cards on the table time. I can't see how the definition of one variable should influence the output of the ISNULL()/COALESCE().  To me ISNULL is broken - each value separated by the comma is isolated and the test being performed is whether the value contained within either is or isn't null. The data type shouldn't have anything to do with it and certainly shouldn't have a bearing on the overall outcome of the function.  This gives me greater reason to exclude the usage of ISNULL from a standards perspective. If I saw a developer actually try and 'leverage' this feature I'd ask for the code to be re-written as I expect there'd have been a better way to write the same thing. In my own mind therefore I will continue to exclude the usage of ISNULL().

However, many thanks to JT for pointing this out - it's certainly a gotcha that I hadn't previously appreciated.

I am off to connect now to file some "feedback" on this feature... I'll update you in due course depending on what the powers say.

Cheers, James

Published 18 July 2008 10:30 by James.Rowland-Jones

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

 

Stuart Toller said:

Interesting, but I disagree with your feeling that it's a bug with ISNULL().  Here's why:

If you repeat the same test but give @value a value of more than 2 characters then both examples return the same result and it's (obviously) two characters.

So with ISNULL() you could get a different length result if the value you're evaluating is null than if the value was not null.

I would think this behaviour is by design as the ISNULL() function is really just a shorthand way of saying (if x is null then return y else return x). It makes sense to me that both paths through this should return the same datatype.  Arguably you could say the same about COALESCE() but just by the definition of the word (to grow together, unite, blend etc.) I suspect there's a different logic going on there under the covers.

What's also interesting (but slightly off topic) is that both functions return an error if you declare @value as an INT but do not if you also give it a numerical value:

DECLARE @value INT

SELECT ISNULL(@value,'test')

SELECT COALESCE(@value,'test')

returns a conversion error, whereas:

DECLARE @value INT

SET @value = 2

SELECT ISNULL(@value,'test')

SELECT COALESCE(@value,'test')

does not.

July 18, 2008 16:03
 

brian filppu said:

even better example

declare @test varchar(2)

Select IsNull(@test, 'test')

select  COALESCE(@test, 'test')

returns:

te

test

July 23, 2008 01:47
 

brian filppu said:

opps, hit enter way to soon, bol says

replacement value for isnull

Is the expression to be returned if check_expression is NULL. replacement_value must be of a type that is implicitly convertible to the type of check_expresssion.

and this produces even different results

declare @test varchar(2)

Select IsNull(@test, 54545)

select  COALESCE(@test, 54545)

results:

*

54545

July 23, 2008 01:53
 

Ben Doherty said:

This is a serious problem that isn't fully documented, but the opposite (flawed) behavior exists as well:

DECLARE @two CHAR(2)          -- Is always 2 characters

DECLARE @five CHAR(5)          -- Is always 5 characters

DECLARE @result VARCHAR(5) -- I.e., variable length

-- Example 1:

SET @two = '22'

SET @five = '55555'

SET @result = COALESCE(@sCode, @dCode)

> @result == '55555' , Correct

-- Example 2

SET @two = '22'

SET @five = NULL

SET @result = COALESCE(@five, @two)

@result == '22   ' (three spaces after), INCORRECT!

This behavior has given me a lot of issues, but it's easily fixed with an RTRIM()

--

Yuck.

December 9, 2008 18:51

Leave a Comment

(required) 
(optional)
(required) 
Submit

About James.Rowland-Jones

James is an Advisory Practice Consultant with EMC Consulting. He works primarily with SQL Server technologies in architecture, development and administration capacities. He also co-ordinates our Internal SQL Server Community.

View James Rowland-Jones's profile on LinkedIn

Powered by Community Server (Personal Edition), by Telligent Systems