NULL means “no value”: it isn’t zero or an empty string. This has consequences that surprise people at first: any operation involving a NULL returns NULL, and a report shows an empty cell where you probably expected a number. COALESCE solves both problems.
COALESCE(value1, value2, ..., valueN)
It returns the first value in the list that isn’t NULL. If all of them are, it returns NULL.
Replacing a NULL with a value
Customer Paul has no bank account on file. With COALESCE we can show some text instead. Since bankAccount is a number and the text isn’t, we first convert the account to text with CAST, because every value in COALESCE must have the same type:
| name | lastName | bankAccount |
|---|---|---|
| Janice | Haynes | 111222333 |
| Mavin | Pettitt | 123456789 |
| Peter | Davis | 998344567 |
| Helen | Ward | 447824556 |
| Kimberly | Lee | 778345112 |
| Jessie | Good | 790763467 |
| Paul | Williams | No account |
Keeping a NULL from spoiling a calculation
If we do a LEFT JOIN from Customers and add up their orders, customers without orders get NULL instead of 0:
| name | withoutCoalesce | withCoalesce |
|---|---|---|
| Janice | 2449.50 | 2449.50 |
| Mavin | 3199.78 | 3199.78 |
| Peter | 4304.25 | 4304.25 |
| Helen | 4999.00 | 4999.00 |
| Kimberly | 899.97 | 899.97 |
| Jessie | NULL | 0 |
| Paul | NULL | 0 |
Jessie and Paul have no orders: SUM has nothing to add and returns NULL. With COALESCE(..., 0) the report shows a 0, which is what a reader expects.
Several fallback values
The advantage of COALESCE over other functions is that it takes any number of arguments and keeps the first one available. It’s handy, for example, to pick the best contact detail among several columns:
SELECT name,
COALESCE(mobilePhone, landline, email, 'No contact') AS contact
FROM Customers;
COALESCE vs ISNULL
SQL Server also has ISNULL(value, replacement), which does the same with two arguments. The differences matter:
COALESCE |
ISNULL |
|
|---|---|---|
| Standard SQL | Yes, works in every database | No, SQL Server only |
| Number of arguments | Two or more | Exactly two |
| Result type | The highest-precedence type among the arguments | The type of the first argument |
The last row causes the most bugs. ISNULL converts the replacement to the type of the first argument, so it can silently truncate text:
DECLARE @code VARCHAR(3) = NULL;
SELECT ISNULL(@code, 'No code') AS withIsnull, -- 'No '
COALESCE(@code, 'No code') AS withCoalesce; -- 'No code'
Unless you only work with SQL Server and need ISNULL for performance in some very specific case, use COALESCE.
NULLIF: the other way round
NULLIF(a, b) returns NULL if both values are equal, and a if they aren’t. Its classic use is avoiding division by zero: if the divisor is 0 it becomes NULL, and the division returns NULL instead of an error.
| product | totalPrice | productQuantity | unitPrice |
|---|---|---|---|
| Xiaomi Mi 11 | 4304.25 | 15 | 286.95 |
| Play Station 5 | 2199.80 | 4 | 549.95 |
| Xbox series X | 999.98 | 2 | 499.99 |
| MacBook Pro M1 | 2449.50 | 1 | 2449.50 |
| Echo DOT 3 | 1999.50 | 50 | 39.99 |
| Echo DOT 4 | 2999.50 | 50 | 59.99 |
| Nintendo Switch | 899.97 | 3 | 299.99 |
| Balay Oven | 1399.10 | 2 | 699.55 |
No order here has 0 units, so every row has a price. Try changing the 0 in NULLIF to 50 and both Echo DOTs become NULL. Combined with COALESCE you can also return a default: COALESCE(totalPrice / NULLIF(productQuantity, 0), 0).
Common mistakes
- Comparing with
= NULL.WHERE bankAccount = NULLnever returns rows. UseIS NULLorIS NOT NULL(see operators). - Mixing types.
COALESCE(bankAccount, 'No account')fails becausebankAccountis numeric; convert it first withCAST. - Using
COALESCEinWHEREon an indexed column.WHERE COALESCE(bankAccount, 0) = 0stops the engine from using an index onbankAccount.WHERE bankAccount = 0 OR bankAccount IS NULLis better.
Practice
- List the orders, showing
'No customer'whencustomerIdisNULL. - What does
COALESCE(NULL, NULL, 3, 4)return? Try it in any of the editors. - For each customer, show the number of orders and the average spend, with 0 instead of
NULLfor customers who haven’t bought anything.