How do you handle NULL values in SQL?
Answer
NULL represents missing, unknown, or undefined data in SQL. NULL values require special handling because they don’t equal anything (including other NULLs) and can affect query results, aggregations, and joins in unexpected ways.
Understanding NULL Behavior
NULL Comparisons
-- NULL comparisons always return UNKNOWN (treated as FALSE)
SELECT * FROM customers WHERE phone = NULL; -- Returns no rows
SELECT * FROM customers WHERE phone != NULL; -- Returns no rows
SELECT * FROM customers WHERE phone <> NULL; -- Returns no rows
-- Correct way to check for NULL
SELECT * FROM customers WHERE phone IS NULL; -- Returns rows with NULL phone
SELECT * FROM customers WHERE phone IS NOT NULL; -- Returns rows with non-NULL phone
Three-Valued Logic
-- SQL uses three-valued logic: TRUE, FALSE, UNKNOWN
SELECT
customer_name,
phone,
CASE
WHEN phone IS NULL THEN 'No Phone'
WHEN phone = '555-0000' THEN 'Default Phone'
ELSE 'Has Phone'
END as phone_status
FROM customers;
NULL Handling Functions
COALESCE()
Returns the first non-NULL value from a list.
-- Basic COALESCE usage
SELECT
customer_name,
COALESCE(phone, email, 'No Contact Info') as contact_method
FROM customers;
-- Multiple column fallback
SELECT
product_name,
COALESCE(sale_price, list_price, cost_price, 0) as display_price
FROM products;
-- Handling NULL in calculations
SELECT
order_id,
quantity,
unit_price,
COALESCE(discount, 0) as discount,
quantity * unit_price * (1 - COALESCE(discount, 0)) as total
FROM order_items;
ISNULL() / IFNULL() / NVL()
Database-specific NULL handling functions.
-- SQL Server: ISNULL()
SELECT
customer_name,
ISNULL(phone, 'No Phone') as phone_display
FROM customers;
-- MySQL: IFNULL()
SELECT
customer_name,
IFNULL(phone, 'No Phone') as phone_display
FROM customers;
-- Oracle: NVL()
SELECT
customer_name,
NVL(phone, 'No Phone') as phone_display
FROM customers;
-- PostgreSQL: COALESCE() is preferred
SELECT
customer_name,
COALESCE(phone, 'No Phone') as phone_display
FROM customers;
NULLIF()
Returns NULL if two expressions are equal.
-- Convert empty strings to NULL
SELECT
customer_name,
NULLIF(phone, '') as phone, -- Empty string becomes NULL
NULLIF(email, '') as email
FROM customers;
-- Avoid division by zero
SELECT
product_name,
total_sales,
total_returns,
total_sales / NULLIF(total_returns, 0) as sales_to_returns_ratio
FROM product_stats;
-- Handle default values
SELECT
employee_name,
NULLIF(department, 'UNASSIGNED') as department -- Convert 'UNASSIGNED' to NULL
FROM employees;
NULL in Aggregations
Aggregate Functions and NULL
-- Sample data with NULLs
CREATE TABLE sales (
salesperson VARCHAR(50),
region VARCHAR(50),
sales_amount DECIMAL(10,2)
);
INSERT INTO sales VALUES
('John', 'North', 1000),
('Jane', 'South', NULL), -- NULL sales amount
('Mike', 'North', 1500),
('Sarah', NULL, 800); -- NULL region
-- Aggregate functions ignore NULL values
SELECT
COUNT(*) as total_rows, -- 4 (counts all rows)
COUNT(sales_amount) as non_null_sales, -- 3 (ignores NULL)
SUM(sales_amount) as total_sales, -- 3300 (ignores NULL)
AVG(sales_amount) as avg_sales, -- 1100 (3300/3, not 3300/4)
MIN(sales_amount) as min_sales, -- 800
MAX(sales_amount) as max_sales -- 1500
FROM sales;
Counting with NULL Considerations
-- Different counting approaches
SELECT
COUNT(*) as all_customers, -- All rows
COUNT(phone) as customers_with_phone, -- Non-NULL phone
COUNT(email) as customers_with_email, -- Non-NULL email
COUNT(CASE WHEN phone IS NOT NULL OR email IS NOT NULL
THEN 1 END) as customers_with_contact -- Has phone OR email
FROM customers;
-- Conditional aggregation with NULLs
SELECT
region,
COUNT(*) as total_sales,
COUNT(sales_amount) as completed_sales,
SUM(COALESCE(sales_amount, 0)) as total_including_zero,
SUM(sales_amount) as total_excluding_null
FROM sales
GROUP BY region;
NULL in JOINs
NULL Values in JOIN Conditions
-- NULLs don't match in JOINs
SELECT
c.customer_name,
c.region,
r.region_manager
FROM customers c
LEFT JOIN regions r ON c.region = r.region_name;
-- Customers with NULL region won't match any region
-- Handling NULL in JOINs
SELECT
c.customer_name,
COALESCE(c.region, 'UNASSIGNED') as region,
COALESCE(r.region_manager, 'No Manager') as manager
FROM customers c
LEFT JOIN regions r ON COALESCE(c.region, 'UNASSIGNED') = COALESCE(r.region_name, 'UNASSIGNED');
Outer JOINs and NULL
-- LEFT JOIN introduces NULLs for unmatched rows
SELECT
c.customer_name,
o.order_id,
CASE
WHEN o.order_id IS NULL THEN 'No Orders'
ELSE 'Has Orders'
END as order_status
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id;
-- Filter out NULL results from outer joins
SELECT
c.customer_name,
o.order_total
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
WHERE o.order_id IS NOT NULL; -- Only customers with orders
NULL in WHERE Clauses
Filtering with NULL Considerations
-- Unexpected results with NULL
SELECT * FROM products WHERE discount != 0.1;
-- Products with NULL discount are excluded (not what you might expect)
-- Correct approach
SELECT * FROM products
WHERE discount != 0.1 OR discount IS NULL;
-- Using COALESCE in WHERE
SELECT * FROM products
WHERE COALESCE(discount, 0) != 0.1;
-- Complex NULL filtering
SELECT
product_name,
price,
discount
FROM products
WHERE
price > 50
AND (discount IS NULL OR discount < 0.2)
AND category IS NOT NULL;
Practical Business Examples
Customer Contact Information
-- Comprehensive contact handling
SELECT
customer_id,
customer_name,
COALESCE(
CASE WHEN phone IS NOT NULL AND phone != '' THEN phone END,
CASE WHEN email IS NOT NULL AND email != '' THEN email END,
CASE WHEN address IS NOT NULL AND address != '' THEN 'Mail Only' END,
'No Contact Info'
) as primary_contact,
CASE
WHEN phone IS NOT NULL AND email IS NOT NULL THEN 'Complete'
WHEN phone IS NOT NULL OR email IS NOT NULL THEN 'Partial'
ELSE 'Missing'
END as contact_completeness
FROM customers;
Sales Performance with NULL Handling
-- Sales report handling missing data
SELECT
salesperson,
region,
COALESCE(SUM(sales_amount), 0) as total_sales,
COUNT(CASE WHEN sales_amount IS NOT NULL THEN 1 END) as completed_sales,
COUNT(*) as total_opportunities,
ROUND(
COUNT(CASE WHEN sales_amount IS NOT NULL THEN 1 END) * 100.0 / COUNT(*),
2
) as completion_rate,
COALESCE(AVG(sales_amount), 0) as avg_sale_amount
FROM sales_opportunities
GROUP BY salesperson, region
ORDER BY total_sales DESC;
Inventory Management
-- Product availability with NULL stock handling
SELECT
product_id,
product_name,
COALESCE(current_stock, 0) as stock_level,
COALESCE(reserved_stock, 0) as reserved,
COALESCE(current_stock, 0) - COALESCE(reserved_stock, 0) as available,
CASE
WHEN current_stock IS NULL THEN 'Stock Unknown'
WHEN current_stock = 0 THEN 'Out of Stock'
WHEN current_stock <= reorder_level THEN 'Low Stock'
ELSE 'In Stock'
END as stock_status,
-- Handle NULL reorder levels
CASE
WHEN reorder_level IS NULL THEN 'No Reorder Policy'
WHEN COALESCE(current_stock, 0) <= reorder_level THEN 'Reorder Required'
ELSE 'Stock OK'
END as reorder_status
FROM products;
NULL in Subqueries and EXISTS
NULL in IN/NOT IN
-- Dangerous: NOT IN with NULL values
SELECT customer_name
FROM customers
WHERE customer_id NOT IN (
SELECT customer_id FROM orders WHERE order_date > '2024-01-01'
);
-- If any order has NULL customer_id, this returns no rows!
-- Safe alternatives
SELECT customer_name
FROM customers c
WHERE NOT EXISTS (
SELECT 1 FROM orders o
WHERE o.customer_id = c.customer_id
AND o.order_date > '2024-01-01'
);
-- Or filter out NULLs
SELECT customer_name
FROM customers
WHERE customer_id NOT IN (
SELECT customer_id FROM orders
WHERE order_date > '2024-01-01'
AND customer_id IS NOT NULL
);
Data Quality and NULL Management
Identifying NULL Patterns
-- Analyze NULL distribution
SELECT
'customers' as table_name,
COUNT(*) as total_rows,
COUNT(phone) as phone_not_null,
COUNT(*) - COUNT(phone) as phone_null,
ROUND((COUNT(*) - COUNT(phone)) * 100.0 / COUNT(*), 2) as phone_null_pct,
COUNT(email) as email_not_null,
COUNT(*) - COUNT(email) as email_null,
ROUND((COUNT(*) - COUNT(email)) * 100.0 / COUNT(*), 2) as email_null_pct
FROM customers;
-- Find records with multiple NULL values
SELECT
customer_id,
customer_name,
CASE WHEN phone IS NULL THEN 1 ELSE 0 END +
CASE WHEN email IS NULL THEN 1 ELSE 0 END +
CASE WHEN address IS NULL THEN 1 ELSE 0 END as null_count
FROM customers
HAVING null_count >= 2;
Data Cleaning with NULL Handling
-- Clean and standardize data
UPDATE customers
SET
phone = NULLIF(TRIM(phone), ''), -- Empty strings to NULL
email = NULLIF(TRIM(LOWER(email)), ''), -- Clean and normalize email
address = NULLIF(TRIM(address), '') -- Empty addresses to NULL
WHERE
phone = '' OR phone = 'N/A' OR phone = 'NULL'
OR email = '' OR email = 'N/A'
OR address = '' OR address = 'N/A';
-- Set default values for critical fields
UPDATE products
SET
reorder_level = COALESCE(reorder_level, 10), -- Default reorder level
category = COALESCE(category, 'UNCATEGORIZED') -- Default category
WHERE reorder_level IS NULL OR category IS NULL;
Best Practices
1. Explicit NULL Checks
-- Always be explicit about NULL handling
SELECT
customer_name,
CASE
WHEN phone IS NOT NULL THEN phone
WHEN email IS NOT NULL THEN email
ELSE 'No Contact'
END as contact_info
FROM customers;
2. Use COALESCE for Defaults
-- Provide meaningful defaults
SELECT
product_name,
COALESCE(description, 'No description available') as description,
COALESCE(weight, 0) as weight,
COALESCE(category, 'Uncategorized') as category
FROM products;
3. Be Careful with Aggregations
-- Consider NULL impact on calculations
SELECT
department,
COUNT(*) as total_employees,
COUNT(salary) as employees_with_salary,
AVG(salary) as avg_salary_excluding_null,
SUM(COALESCE(salary, 0)) / COUNT(*) as avg_salary_including_null_as_zero
FROM employees
GROUP BY department;
4. Document NULL Handling Strategy
-- Document your NULL handling approach
/*
NULL Handling Strategy for Orders Table:
- customer_id: Never NULL (enforced by NOT NULL constraint)
- shipping_address: NULL allowed for digital products
- discount: NULL means no discount (treat as 0 in calculations)
- notes: NULL allowed (empty notes)
- estimated_delivery: NULL for immediate/digital delivery
*/
Interview Tips
- Understand that NULL represents unknown/missing data, not zero or empty string
- Know that NULL comparisons require IS NULL/IS NOT NULL operators
- Be familiar with COALESCE, ISNULL, NULLIF functions
- Understand how NULL affects aggregations (ignored by most functions)
- Know the dangers of NOT IN with NULL values
- Practice NULL handling in JOINs and subqueries
- Be able to explain three-valued logic (TRUE, FALSE, UNKNOWN)
Test Your Knowledge
Take a quick quiz to test your understanding of this topic.