Handling NULL Values
Work with NULL values using IS NULL and IS NOT NULL.
Introduction to NULL Values
In SQL, NULL is special - it represents the absence of a value. It's not zero, not an empty string, not false. It means "unknown" or "not applicable."
NULL values appear everywhere in real databases:
- An employee's bonus might be NULL (not determined yet)
- A customer's phone number might be NULL (not provided)
- An order's delivery_date might be NULL (not shipped yet)
Understanding how NULL behaves is crucial because it acts differently than you might expect!
A special marker in SQL that indicates missing or unknown data. NULL is not equal to anything, not even another NULL. It represents the absence of a value.
Common misconceptions:
- NULL is NOT zero (0)
- NULL is NOT an empty string ('')
- NULL is NOT false
- NULL IS NOT equal to NULL!
Let's work with data that contains NULL values:
id | name | department | salary | bonus | phone |
|---|---|---|---|---|---|
| 1 | Alice | Engineering | 95000 | 5000 | 555-0101 |
| 2 | Bob | Marketing | 75000 | NULL | 555-0102 |
| 3 | Charlie | Engineering | 85000 | 8000 | NULL |
| 4 | Diana | Sales | 70000 | NULL | NULL |
NULL in Comparisons - The Unexpected Behavior
Here's the biggest surprise about NULL: NULL is not equal to anything, including NULL itself!
-- Try to find employees with no bonus using = NULL (WRONG!)
SELECT name, bonus
FROM employees
WHERE bonus = NULL;This returned zero rows even though Bob and Diana have NULL bonuses! Why?
Because bonus = NULL always evaluates to NULL (unknown), not TRUE or FALSE. And WHERE filters only keep rows where the condition is TRUE.
The correct way - use IS NULL:
-- Find employees with no bonus (CORRECT)
SELECT name, bonus
FROM employees
WHERE bonus IS NULL;IS NULL is the special operator for checking if a value is NULL.
To find non-NULL values, use IS NOT NULL:
-- Find employees who HAVE a bonus
SELECT name, bonus
FROM employees
WHERE bonus IS NOT NULL;Critical Rule:
- Use IS NULL to check for NULL
- Use IS NOT NULL to check for non-NULL
- NEVER use
= NULLor!= NULL- they don't work!
NULL in Arithmetic Operations
Any arithmetic operation with NULL results in NULL:
-- Calculate total compensation
SELECT
name,
salary,
bonus,
salary + bonus AS total_compensation
FROM employees;Bob and Diana's total_compensation is NULL because 75000 + NULL = NULL and 70000 + NULL = NULL.
NULL propagates through calculations:
10 + NULL = NULL100 * NULL = NULLNULL / 5 = NULLNULL - NULL = NULL
COALESCE - Providing Default Values
COALESCE() returns the first non-NULL value from a list:
-- Treat NULL bonus as 0
SELECT
name,
salary,
bonus,
COALESCE(bonus, 0) AS bonus_or_zero,
salary + COALESCE(bonus, 0) AS total_compensation
FROM employees;Perfect! Now everyone has a valid total_compensation.
COALESCE with multiple fallbacks:
-- Try multiple columns, use first non-NULL
SELECT
name,
COALESCE(phone, email, 'No contact info') AS contact
FROM employees;COALESCE checks phone first, then email, then uses the default string.
NULLIF - Creating NULL Values
NULLIF() returns NULL if two values are equal, otherwise returns the first value:
-- Convert empty strings to NULL
SELECT
name,
NULLIF(phone, '') AS phone_cleaned
FROM employees;Useful for:
- Converting sentinel values (like -1 or '') to NULL
- Avoiding division by zero
-- Avoid division by zero
SELECT
total_sales,
order_count,
total_sales / NULLIF(order_count, 0) AS average_order
FROM sales_summary;If order_count is 0, NULLIF returns NULL, and the division returns NULL instead of causing an error.
NULL in Aggregate Functions
Most aggregate functions ignore NULL values:
-- Aggregates ignore NULL
SELECT
COUNT(*) AS total_employees,
COUNT(bonus) AS employees_with_bonus,
SUM(bonus) AS total_bonuses,
AVG(bonus) AS avg_bonus
FROM employees;Notice:
COUNT(*)counts all 4 employeesCOUNT(bonus)counts only 2 (Alice and Charlie have bonuses)SUM(bonus)= 5000 + 8000 = 13000 (NULLs ignored)AVG(bonus)= 13000 / 2 = 6500 (divides by non-NULL count)
Important: AVG ignores NULLs when calculating the average. If you want to treat NULL as 0:
-- Include NULL bonuses as 0 in average
SELECT
AVG(COALESCE(bonus, 0)) AS avg_bonus_including_zero
FROM employees;Now it's (5000 + 0 + 8000 + 0) / 4 = 3250, which is different!
NULL in Logical Operations (AND, OR, NOT)
NULL in logical operations introduces three-valued logic: TRUE, FALSE, and NULL (unknown).
Condition 1 | Condition 2 | Result |
|---|---|---|
| TRUE | TRUE | TRUE |
| TRUE | FALSE | FALSE |
| TRUE | NULL | NULL |
| FALSE | NULL | FALSE |
| NULL | NULL | NULL |
Condition 1 | Condition 2 | Result |
|---|---|---|
| TRUE | NULL | TRUE |
| FALSE | NULL | NULL |
| NULL | NULL | NULL |
-- Unexpected behavior with NULL in WHERE
SELECT name, bonus, phone
FROM employees
WHERE bonus > 5000 OR phone IS NULL;Bob (bonus=NULL, phone='555-0102') is excluded because:
bonus > 5000evaluates to NULL (NULL comparison)phone IS NULLevaluates to FALSE (he has a phone)NULL OR FALSE = NULL(not TRUE, so excluded)
NULL in ORDER BY
How does ORDER BY handle NULL values?
-- Sort by bonus (ascending)
SELECT name, bonus
FROM employees
ORDER BY bonus ASC;In most databases (MySQL, PostgreSQL):
- NULL appears first with ASC (ascending)
- NULL appears last with DESC (descending)
Control NULL ordering:
-- Put NULLs last even with ASC (PostgreSQL syntax)
SELECT name, bonus
FROM employees
ORDER BY bonus ASC NULLS LAST;Database differences:
- MySQL/PostgreSQL: NULL first with ASC, last with DESC
- SQL Server: NULL first with ASC, last with DESC
- Oracle: NULL last with ASC, first with DESC
PostgreSQL and Oracle support NULLS FIRST and NULLS LAST keywords.
NULL in DISTINCT and GROUP BY
DISTINCT treats all NULL values as equal (one NULL appears in results):
-- Get distinct bonus values
SELECT DISTINCT bonus
FROM employees
ORDER BY bonus;Only one NULL appears, even though Bob and Diana both have NULL bonuses.
GROUP BY also treats NULLs as equal:
-- Count employees by bonus
SELECT bonus, COUNT(*) AS employee_count
FROM employees
GROUP BY bonus;Bob and Diana are grouped together under NULL.
Common Mistakes to Avoid
Don't make these mistakes:
-
Using = NULL or != NULL: ❌
- Wrong:
WHERE bonus = NULL - Right:
WHERE bonus IS NULL
- Wrong:
-
Forgetting NULL in arithmetic: ❌
salary + bonusreturns NULL if bonus is NULL- Use:
salary + COALESCE(bonus, 0)
-
Using COUNT(*) when you mean COUNT(column): ❌
COUNT(*)counts all rows (including NULL)COUNT(column)counts non-NULL values
-
Assuming NULL = 0 or NULL = '': ❌
- NULL is distinct from 0 and empty string
- Must handle explicitly
-
Not handling NULL in AVG: ❌
- AVG ignores NULL (doesn't count them in average)
- Decide if you want to treat NULL as 0
-
Comparing two NULLs with =: ❌
NULL = NULLis NULL (unknown), not TRUE- Use
IS NULLfor comparisons
-
Unexpected NULL propagation: ❌
WHERE price > 100 OR price <= 100doesn't catch NULL prices- Add:
OR price IS NULLif needed
-
Not using COALESCE for user-facing output: ❌
- Displaying NULL to users is confusing
- Use COALESCE to show 'N/A', '0', or meaningful defaults
Practical Examples
Example 1: Customer contact report
-- Show customers with contact info, flag missing data
SELECT
name,
COALESCE(email, 'No email') AS email,
COALESCE(phone, 'No phone') AS phone,
CASE
WHEN email IS NULL AND phone IS NULL THEN 'No contact info'
WHEN email IS NULL THEN 'Missing email'
WHEN phone IS NULL THEN 'Missing phone'
ELSE 'Complete'
END AS status
FROM customers;Example 2: Product pricing with discounts
-- Calculate final price (NULL discount means no discount)
SELECT
product_name,
price,
discount,
price - COALESCE(discount, 0) AS final_price
FROM products;Example 3: Employee bonus report
-- Report showing bonus status
SELECT
name,
department,
salary,
COALESCE(bonus, 0) AS bonus,
salary + COALESCE(bonus, 0) AS total_comp,
CASE
WHEN bonus IS NULL THEN 'No bonus'
WHEN bonus > 5000 THEN 'High bonus'
ELSE 'Standard bonus'
END AS bonus_category
FROM employees
ORDER BY total_comp DESC;Example 4: Finding incomplete records
-- Find employees missing critical information
SELECT
name,
department,
CASE
WHEN phone IS NULL THEN 'Missing phone'
ELSE 'OK'
END AS phone_status,
CASE
WHEN department IS NULL THEN 'Missing department'
ELSE 'OK'
END AS dept_status
FROM employees
WHERE phone IS NULL
OR department IS NULL;Key Takeaways
What you learned:
✅ NULL represents missing or unknown data ✅ NULL is NOT equal to 0, '', or false ✅ Use IS NULL and IS NOT NULL for comparisons (never = NULL) ✅ COALESCE() provides default values for NULL ✅ NULLIF() converts specific values to NULL ✅ Arithmetic with NULL returns NULL (NULL propagation) ✅ Aggregate functions ignore NULL (except COUNT(*)) ✅ NULL in WHERE creates three-valued logic (TRUE/FALSE/NULL) ✅ ORDER BY, DISTINCT, and GROUP BY have special NULL handling ✅ Always consider NULL when writing queries!
Coming up next: We'll learn about string functions to manipulate and transform text data!
Practice Exercise: Try these queries:
- Find employees with NULL bonuses
- Calculate total compensation treating NULL bonus as 0
- Count employees who have vs don't have phone numbers
- Show all employees, with 'No phone' for NULL phone values
- Find average bonus including NULLs as 0