COALESCE — First Non-NULL Value
title: “COALESCE (Advanced NULL Handling)” part: 2 topic_number: 5 slug: “coalesce” difficulty: “Intermediate” prerequisites: “null-handling” —
COALESCE — First Non-NULL Value
What Is It?
COALESCE() returns the first non-NULL value from a list of arguments. It’s a more powerful version of IFNULL that can check multiple columns or expressions in one go.
Real-world analogy: Like a series of backup plans — “Try option A; if that’s not available, try B; if not, try C…” until you find one that works.
When you’d use it:
- Provide default values for NULL columns
- Choose between multiple optional data sources
- Create fallback chains
- Clean up reports with meaningful defaults
Syntax Breakdown
1
COALESCE(value1, value2, value3, ..., defaultValue)
How it works:
- Evaluates arguments left to right
- Returns the first non-NULL value
- If all are NULL, returns NULL (unless you provide a final default)
vs IFNULL:
IFNULL(col, default)— checks only ONE columnCOALESCE(col1, col2, col3, default)— checks MULTIPLE values
Mental Model — Left-to-Right Scan
Think of COALESCE as scanning a list from left to right and stopping at the first real value:
1
2
3
4
5
6
7
8
COALESCE(mobile_phone, office_phone, 'No phone')
↓
Is mobile_phone NULL?
├── NO → return mobile_phone ← stops here
└── YES → check next...
Is office_phone NULL?
├── NO → return office_phone
└── YES → return 'No phone'
Quick decision: use IFNULL when you have one column and one fallback. Use COALESCE when you have two or more columns to try in order.
Basic Examples
Simple Default Value
1
2
3
4
5
6
7
SELECT
first_name,
last_name,
email,
COALESCE(email, '[email protected]') AS email_with_default
FROM employees
LIMIT 5;
Expected output:
| first_name | last_name | email_with_default | |
|---|---|---|---|
| Alice | Johnson | NULL | [email protected] |
| Bob | Smith | [email protected] | [email protected] |
| Carol | Williams | NULL | [email protected] |
| David | Brown | [email protected] | [email protected] |
| Eve | Davis | [email protected] | [email protected] |
Replaces NULL emails with a default.
Multiple Fallbacks
Show each employee’s “best available” contact — preferring a specific email format, falling back to manager info, then a default:
1
2
3
4
5
6
7
8
9
10
11
-- Using columns that already exist in our company DB
SELECT
e.first_name,
e.last_name,
COALESCE(
e.email,
CONCAT('reports-to-emp-', e.manager_id, '@company.com'),
'[email protected]'
) AS best_contact
FROM employees e
LIMIT 8;
Expected output:
| first_name | last_name | best_contact |
|---|---|---|
| Alice | Johnson | [email protected] |
| Bob | Smith | [email protected] |
| Paul | Garcia | [email protected] |
What happened: Checks email first. If NULL, builds a fallback from manager_id. If manager_id is also NULL, uses the final default.
Going Deeper
COALESCE with Calculations
Calculate commission (prefer actual commission, fallback to salary * 5%):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- Add commission column for demo
ALTER TABLE employees ADD COLUMN commission DECIMAL(10, 2);
UPDATE employees SET commission = 5000 WHERE employee_id IN (3, 5, 11);
SELECT
first_name,
last_name,
salary,
commission,
COALESCE(commission, salary * 0.05) AS effective_commission
FROM employees
ORDER BY salary DESC
LIMIT 8;
Expected output:
| first_name | last_name | salary | commission | effective_commission |
|---|---|---|---|---|
| Sam | Clark | 95000.00 | NULL | 4750.00 |
| Frank | Miller | 93000.00 | NULL | 4650.00 |
| Henry | Moore | 92000.00 | NULL | 4600.00 |
| Paul | Garcia | 87000.00 | NULL | 4350.00 |
| Bob | Smith | 82000.00 | NULL | 4100.00 |
| Carol | Williams | 78000.00 | 5000.00 | 5000.00 |
| Alice | Johnson | 75000.00 | NULL | 3750.00 |
| Ivy | Anderson | 74000.00 | 5000.00 | 5000.00 |
Uses actual commission if available, otherwise calculates 5%.
Complex Data Merging
Show best available address from multiple sources:
1
2
3
4
5
6
7
8
9
10
11
SELECT
employee_id,
first_name,
COALESCE(
NULLIF(preferred_address, ''),
NULLIF(mailing_address, ''),
NULLIF(home_address, ''),
'Address on file'
) AS primary_address
FROM employees
LIMIT 5;
Key technique: NULLIF(col, '') treats empty strings as NULL, so COALESCE skips them too!
Conditional Defaults with CASE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SELECT
first_name,
last_name,
department_id,
salary,
COALESCE(
commission,
CASE
WHEN department_id = 3 THEN salary * 0.10 -- Sales gets 10%
WHEN department_id = 2 THEN salary * 0.07 -- Marketing gets 7%
ELSE salary * 0.05 -- Others get 5%
END
) AS total_commission
FROM employees
ORDER BY total_commission DESC
LIMIT 10;
Expected output:
| first_name | last_name | department_id | salary | total_commission |
|---|---|---|---|---|
| Frank | Miller | 2 | 93000.00 | 6510.00 |
| Bob | Smith | 3 | 82000.00 | 8200.00 |
| Carol | Williams | 3 | 78000.00 | 5000.00 |
| … | … | … | … | … |
Uses actual commission if exists, otherwise calculates based on department.
Pause and Predict: What does
COALESCE(NULL, NULL, NULL)return?
Answer
`NULL` If ALL arguments are NULL, COALESCE returns NULL. Always provide a non-NULL default at the end if you want to avoid this: `COALESCE(val1, val2, 'default')`.Watch Out — Common Mistakes
Mistake #1: Not Providing a Final Default
1
2
3
-- CAN STILL RETURN NULL
SELECT COALESCE(email, phone) AS contact
FROM employees;
Problem: If both email AND phone are NULL, result is NULL.
Fix: Add a non-NULL default:
1
2
3
-- • ALWAYS HAS A VALUE
SELECT COALESCE(email, phone, 'No contact info') AS contact
FROM employees;
Mistake #2: Type Mismatches
1
2
-- TYPE ERROR (mixing numbers and strings)
SELECT COALESCE(salary, 'Unknown');
Error: MySQL tries to convert types, leading to unexpected results (salary might be converted to string ‘0’).
Fix: Keep types consistent:
1
2
3
4
5
-- • CORRECT (all strings)
SELECT COALESCE(CAST(salary AS CHAR), 'Unknown');
-- Or (all numbers)
SELECT COALESCE(salary, 0);
Mistake #3: Forgetting Empty Strings Aren’t NULL
1
2
3
-- MISSES EMPTY STRINGS
SELECT COALESCE(email, '[email protected]')
FROM employees;
Problem: If email is '' (empty string), COALESCE sees it as a value (not NULL) and uses it.
Fix: Use NULLIF to treat empty strings as NULL:
1
2
3
-- • CORRECT
SELECT COALESCE(NULLIF(email, ''), '[email protected]')
FROM employees;
Edge Case Spotlight
COALESCE vs CASE WHEN
These are equivalent:
1
2
3
4
5
6
7
8
9
10
-- Using COALESCE
SELECT COALESCE(email, phone, 'No contact') AS contact;
-- Using CASE WHEN
SELECT
CASE
WHEN email IS NOT NULL THEN email
WHEN phone IS NOT NULL THEN phone
ELSE 'No contact'
END AS contact;
COALESCE is shorter and clearer for this pattern!
Performance Consideration
1
2
3
4
5
6
7
8
-- INEFFICIENT (recalculates expression)
SELECT
COALESCE(
(SELECT AVG(salary) FROM employees WHERE department_id = e.department_id),
(SELECT AVG(salary) FROM employees WHERE department_id = e.department_id),
0
)
FROM employees e;
Problem: Subquery runs multiple times unnecessarily.
Fix: Calculate once, then use COALESCE:
1
2
3
4
5
6
7
8
9
-- • BETTER
SELECT
COALESCE(dept_avg.avg_sal, 0) AS avg_salary
FROM employees e
LEFT JOIN (
SELECT department_id, AVG(salary) AS avg_sal
FROM employees
GROUP BY department_id
) dept_avg ON e.department_id = dept_avg.department_id;
Try This
Exercise 1 (Guided)
Create a “display name” for employees using this priority:
- If they have a nickname, use it
- Otherwise use first_name
- If both are NULL, use ‘Employee #’ + employee_id
Add a nickname column first: ALTER TABLE employees ADD COLUMN nickname VARCHAR(50);
Then set a few: UPDATE employees SET nickname = 'Ali' WHERE employee_id = 1;
Hint
COALESCE(nickname, first_name, CONCAT('Employee #', employee_id))Exercise 2 (Independent)
Show all employees with their “best contact method”: check email, then mobile_phone, then office_phone, then show ‘URGENT: Update contact info’. Also show which method was used (e.g., ‘Email’, ‘Mobile’, ‘Office’, ‘None’).
Hint
Use COALESCE for the contact value, and CASE WHEN to determine which method was used.Exercise 3 (Challenge)
Calculate “total compensation” for each employee:
- Start with salary
- Add commission if available
- If no commission but department is Sales (dept_id = 3), add 10% of salary
- Add a “retention bonus” of $5000 if hire_date is before 2020
- Show first_name, last_name, salary, and total_compensation
Hint
Combine COALESCE with CASE WHEN and date comparisons. Total = salary + COALESCE(commission, ...) + CASE...Answer Key
Exercise 1 Answer
```sql -- Setup ALTER TABLE employees ADD COLUMN nickname VARCHAR(50); UPDATE employees SET nickname = 'Ali' WHERE employee_id = 1; UPDATE employees SET nickname = 'Bobby' WHERE employee_id = 2; UPDATE employees SET nickname = 'Dave' WHERE employee_id = 4; -- Solution SELECT employee_id, first_name, nickname, COALESCE( nickname, first_name, CONCAT('Employee #', employee_id) ) AS display_name FROM employees ORDER BY employee_id LIMIT 10; ``` **Expected output:** | employee_id | first_name | nickname | display_name | |-------------|------------|----------|--------------| | 1 | Alice | Ali | Ali | | 2 | Bob | Bobby | Bobby | | 3 | Carol | NULL | Carol | | 4 | David | Dave | Dave | | 5 | Eve | NULL | Eve | | ... | ... | ... | ... | Uses nickname if available, falls back to first_name.Exercise 2 Answer
```sql SELECT first_name, last_name, COALESCE( email, mobile_phone, office_phone, 'URGENT: Update contact info' ) AS best_contact, CASE WHEN email IS NOT NULL THEN 'Email' WHEN mobile_phone IS NOT NULL THEN 'Mobile' WHEN office_phone IS NOT NULL THEN 'Office' ELSE 'None' END AS contact_method FROM employees ORDER BY last_name; ``` **Expected output:** | first_name | last_name | best_contact | contact_method | |------------|-----------|--------------|----------------| | Jack | Anderson | 555-1234 | Mobile | | David | Brown | [email protected] | Email | | Sam | Clark | URGENT: Update contact info | None | | ... | ... | ... | ... | Shows best contact and identifies the source.Exercise 3 Answer
```sql SELECT first_name, last_name, salary, salary + COALESCE( commission, CASE WHEN department_id = 3 THEN salary * 0.10 ELSE 0 END ) + CASE WHEN hire_date < '2020-01-01' THEN 5000 ELSE 0 END AS total_compensation FROM employees ORDER BY total_compensation DESC LIMIT 10; ``` **Expected output:** | first_name | last_name | salary | total_compensation | |------------|-----------|---------|-------------------| | Sam | Clark | 95000.00 | 100000.00 | | Frank | Miller | 93000.00 | 103650.00 | | Henry | Moore | 92000.00 | 97000.00 | | Bob | Smith | 82000.00 | 95200.00 | | Carol | Williams | 78000.00 | 90800.00 | | ... | ... | ... | ... | Complete compensation calculation with all bonuses. **Breakdown:** - Base salary - + Commission (or 10% for Sales dept) - + $5K retention bonus if hired pre-2020Quick Recap
• COALESCE returns first non-NULL value from a list
• More powerful than IFNULL (checks multiple values)
• Always provide a final non-NULL default to avoid NULL results
• All arguments must be compatible data types
• Use NULLIF to treat empty strings as NULL
• Shorter and clearer than equivalent CASE WHEN for NULL checks
• Common uses: default values, fallback chains, data merging
Up Next
Next topic: Nested Subqueries (Subqueries in SELECT/WHERE) → part2_06_nested_subqueries.md
Type ‘next’ when ready to continue!