Skip to main content

SQL COALESCE, NULLIF, IFNULL for Handling NULL

NULL is one of the most misunderstood concepts in SQL. It does not mean zero. It does not mean an empty string. It means unknown or absent. A customer whose phone number is NULL might have a phone; you just do not know the number. An order whose ship date is NULL has not been shipped yet. This distinction matters because NULL behaves differently from every other value in SQL, and ignoring that behavior leads to silent bugs that corrupt calculations, break comparisons, and produce wrong results without any error message.

SQL COALESCE and its companion functions NULLIF, IFNULL, and ISNULL are the tools that let you handle NULL values safely and predictably. They replace missing values with defaults, create NULLs strategically, and keep your queries from falling into the many traps that NULL sets for unsuspecting developers.

This guide explains how NULL actually works, covers every major NULL-handling function with practical examples, and provides the strategies you need to write queries that produce correct results even when data is incomplete.

Why NULL Is Different from Everything Else

Before diving into functions, you need to understand why NULL requires special handling. Consider these facts:

SELECT NULL = NULL;       -- Result: NULL (not TRUE!)
SELECT NULL != NULL; -- Result: NULL (not TRUE!)
SELECT NULL > 0; -- Result: NULL
SELECT NULL + 10; -- Result: NULL
SELECT NULL || 'hello'; -- Result: NULL (PostgreSQL)
SELECT NOT NULL; -- Result: NULL

NULL is not equal to anything, including itself. Any operation involving NULL produces NULL. Any comparison with NULL returns NULL (which is treated as FALSE in WHERE clauses). This is called three-valued logic: every boolean expression evaluates to TRUE, FALSE, or UNKNOWN (which is what NULL represents in boolean context).

This creates real problems:

-- Bug: this misses rows where phone IS NULL
SELECT * FROM contacts WHERE phone != '555-0100';
-- Rows with phone = NULL are NOT included because NULL != '555-0100' is UNKNOWN

-- Bug: this sum ignores NULLs silently
SELECT SUM(bonus) FROM employees;
-- If some bonuses are NULL, they are skipped, not treated as 0

-- Bug: this count is wrong
SELECT COUNT(email) FROM contacts;
-- Only counts rows where email IS NOT NULL
-- COUNT(*) counts all rows, COUNT(column) skips NULLs

The Sample Data

All examples use these tables:

CREATE TABLE customers (
customer_id INT PRIMARY KEY,
first_name VARCHAR(50),
last_name VARCHAR(50),
email VARCHAR(100),
phone VARCHAR(20),
city VARCHAR(50),
loyalty_points INT
);

CREATE TABLE products (
product_id INT PRIMARY KEY,
product_name VARCHAR(100),
price DECIMAL(10,2),
sale_price DECIMAL(10,2),
description VARCHAR(500),
stock_qty INT
);

CREATE TABLE orders (
order_id INT PRIMARY KEY,
customer_id INT,
order_date DATE,
ship_date DATE,
discount_pct DECIMAL(5,2),
notes VARCHAR(200),
total_amount DECIMAL(10,2)
);

INSERT INTO customers VALUES
(1, 'Alice', 'Martin', 'alice@example.com', '555-0101', 'New York', 250),
(2, 'Bob', 'Jones', 'bob@example.com', NULL, 'Chicago', NULL),
(3, 'Carol', 'Smith', NULL, '555-0103', NULL, 100),
(4, 'Dave', 'Wilson', 'dave@example.com', '555-0104', 'Boston', 0),
(5, 'Eve', 'Turner', NULL, NULL, 'Denver', NULL);

INSERT INTO products VALUES
(101, 'Wireless Mouse', 25.99, 19.99, 'Ergonomic wireless mouse', 150),
(102, 'USB-C Cable', 9.99, NULL, 'Braided 2m cable', 300),
(103, 'Notebook A5', 4.50, 3.99, NULL, 500),
(104, 'Mechanical Keyboard', 89.99, NULL, 'Cherry MX switches', 0),
(105, 'Desk Lamp', 34.50, 29.99, '', 3),
(106, 'Standing Desk', 299.99, NULL, 'Height-adjustable standing desk', 12);

INSERT INTO orders VALUES
(1001, 1, '2024-01-15', '2024-01-18', 10.00, 'Gift wrap requested', 155.97),
(1002, 2, '2024-03-22', '2024-03-25', NULL, NULL, 299.99),
(1003, 1, '2024-06-05', NULL, 5.00, 'Fragile items', 45.48),
(1004, 3, '2024-06-10', NULL, NULL, NULL, 89.99),
(1005, 4, '2024-06-14', NULL, 0.00, '', 9.99);
note

Notice the deliberately messy data: NULL phones, emails, cities, sale prices, descriptions, ship dates, discounts, and notes throughout.

COALESCE: The Essential NULL Replacement

COALESCE returns the first non-NULL value from a list of arguments. It is the most important NULL-handling function in SQL and is supported by every major database.

Syntax

COALESCE(value1, value2, value3, ...)

The function evaluates arguments left to right and returns the first one that is not NULL. If all arguments are NULL, it returns NULL.

Basic Examples

SELECT
COALESCE(NULL, 'fallback') AS example1,
COALESCE(NULL, NULL, 'third') AS example2,
COALESCE('first', 'second') AS example3,
COALESCE(NULL, NULL, NULL) AS example4;
example1example2example3example4
fallbackthirdfirstNULL

Providing Default Values for NULL Columns

The most common use of COALESCE: replace NULL with a meaningful default.

SELECT
first_name,
COALESCE(email, 'No email on file') AS email,
COALESCE(phone, 'No phone on file') AS phone,
COALESCE(city, 'Unknown') AS city,
COALESCE(loyalty_points, 0) AS loyalty_points
FROM customers;
first_nameemailphonecityloyalty_points
Alicealice@example.com555-0101New York250
Bobbob@example.comNo phone on fileChicago0
CarolNo email on file555-0103Unknown100
Davedave@example.com555-0104Boston0
EveNo email on fileNo phone on fileDenver0

Every NULL is replaced with a human-readable default. The query never returns NULL for any of these columns.

Fallback Chain: Multiple Alternatives

COALESCE accepts any number of arguments, creating a priority chain of fallback values:

-- Use sale_price if available, otherwise regular price
SELECT
product_name,
price AS regular_price,
sale_price,
COALESCE(sale_price, price) AS effective_price
FROM products;
product_nameregular_pricesale_priceeffective_price
Wireless Mouse25.9919.9919.99
USB-C Cable9.99NULL9.99
Notebook A54.503.993.99
Mechanical Keyboard89.99NULL89.99
Desk Lamp34.5029.9929.99
Standing Desk299.99NULL299.99

Three-level fallback for contact information:

-- Best available contact method
SELECT
first_name,
COALESCE(email, phone, 'No contact info') AS best_contact
FROM customers;
first_namebest_contact
Alicealice@example.com
Bobbob@example.com
Carol555-0103
Davedave@example.com
EveNo contact info

Alice, Bob, and Dave have emails (checked first). Carol has no email but has a phone (checked second). Eve has neither, so the default string is used (third).

COALESCE in Calculations

NULL in arithmetic makes the entire result NULL. COALESCE prevents this:

-- Without COALESCE: NULL discount makes the total NULL
SELECT
order_id,
total_amount,
discount_pct,
total_amount * (1 - discount_pct / 100) AS wrong_total
FROM orders;
order_idtotal_amountdiscount_pctwrong_total
1001155.9710.00140.373
1002299.99NULLNULL
100345.485.0043.206
100489.99NULLNULL
10059.990.009.99

Orders 1002 and 1004 have NULL discounts, causing their totals to be NULL.

-- With COALESCE: NULL discount treated as 0%
SELECT
order_id,
total_amount,
COALESCE(discount_pct, 0) AS discount_pct,
ROUND(total_amount * (1 - COALESCE(discount_pct, 0) / 100), 2) AS final_total
FROM orders;
order_idtotal_amountdiscount_pctfinal_total
1001155.9710.00140.37
1002299.990.00299.99
100345.485.0043.21
100489.990.0089.99
10059.990.009.99

Every row now has a valid total.

COALESCE in Aggregation

-- SUM skips NULLs silently
SELECT SUM(loyalty_points) AS total_points FROM customers;
-- Result: 350 (only counts 250 + 100 + 0, skips NULLs for Bob and Eve)

-- Use COALESCE to treat NULL as 0
SELECT SUM(COALESCE(loyalty_points, 0)) AS total_points FROM customers;
-- Result: 350 (same in this case because SUM already ignores NULLs)

-- But AVG is different!
SELECT AVG(loyalty_points) AS avg_points FROM customers;
-- Result: 116.67 (350 / 3, only counts non-NULL rows)

SELECT AVG(COALESCE(loyalty_points, 0)) AS avg_points FROM customers;
-- Result: 70.00 (350 / 5, counts all rows)
warning

AVG and COUNT(column) silently skip NULL values, which changes the denominator. If Bob and Eve genuinely have zero points (not unknown), use COALESCE to include them. If their points are truly unknown, skipping them may be correct. The business context determines the right approach.

-- COUNT(*) counts all rows: 5
-- COUNT(loyalty_points) counts non-NULL only: 3
SELECT
COUNT(*) AS all_rows,
COUNT(loyalty_points) AS non_null_rows
FROM customers;

COALESCE in String Concatenation

-- PostgreSQL: || returns NULL if any operand is NULL
SELECT first_name || ' ' || last_name || ' (' || city || ')' AS display
FROM customers;
display
Alice Martin (New York)
Bob Jones (Chicago)
NULL
Dave Wilson (Boston)
Eve Turner (Denver)

Carol's entire string is NULL because her city is NULL.

-- Fix with COALESCE
SELECT
first_name || ' ' || last_name ||
' (' || COALESCE(city, 'Location unknown') || ')' AS display
FROM customers;
display
Alice Martin (New York)
Bob Jones (Chicago)
Carol Smith (Location unknown)
Dave Wilson (Boston)
Eve Turner (Denver)

NULLIF: Creating NULLs Strategically

NULLIF does the opposite of COALESCE. Instead of replacing NULL with a value, it creates NULL when two values are equal.

Syntax

NULLIF(expression1, expression2)

Returns NULL if expression1 = expression2. Otherwise returns expression1.

SELECT
NULLIF(10, 10) AS same_values, -- NULL
NULLIF(10, 20) AS diff_values, -- 10
NULLIF('abc', 'abc') AS same_str, -- NULL
NULLIF('abc', 'xyz') AS diff_str; -- 'abc'
same_valuesdiff_valuessame_strdiff_str
NULL10NULLabc

The Primary Use: Preventing Division by Zero

This is by far the most common reason to use NULLIF:

-- Without NULLIF: crashes on division by zero
SELECT product_name, price / stock_qty AS price_per_unit
FROM products;
-- ERROR: division by zero (Mechanical Keyboard has stock_qty = 0)

-- With NULLIF: returns NULL instead of crashing
SELECT
product_name,
stock_qty,
price / NULLIF(stock_qty, 0) AS price_per_unit
FROM products;
product_namestock_qtyprice_per_unit
Wireless Mouse1500.17
USB-C Cable3000.03
Notebook A55000.01
Mechanical Keyboard0NULL
Desk Lamp311.50
Standing Desk1225.00

NULLIF(stock_qty, 0) turns zero into NULL. Dividing by NULL returns NULL instead of an error. You can then wrap the whole expression in COALESCE to show a default:

SELECT
product_name,
COALESCE(price / NULLIF(stock_qty, 0), 0) AS price_per_unit
FROM products;
product_nameprice_per_unit
Wireless Mouse0.17
USB-C Cable0.03
Notebook A50.01
Mechanical Keyboard0
Desk Lamp11.50
Standing Desk25.00

Now the Mechanical Keyboard shows 0 instead of NULL.

Treating Empty Strings as NULL

Imported data often has empty strings ('') where NULL would be more appropriate:

-- The Desk Lamp has description = '' (empty string, not NULL)
-- The Notebook has description = NULL
SELECT
product_name,
description,
NULLIF(description, '') AS cleaned_description
FROM products;
product_namedescriptioncleaned_description
Wireless MouseErgonomic wireless mouseErgonomic wireless mouse
USB-C CableBraided 2m cableBraided 2m cable
Notebook A5NULLNULL
Desk LampNULLNULL
Standing DeskHeight-adjustable standing deskHeight-adjustable standing desk

The Desk Lamp's empty string becomes NULL, making it consistent with the Notebook's actual NULL. This is useful before applying COALESCE:

SELECT
product_name,
COALESCE(NULLIF(description, ''), 'No description available') AS description
FROM products;
product_namedescription
Wireless MouseErgonomic wireless mouse
USB-C CableBraided 2m cable
Notebook A5No description available
Mechanical KeyboardCherry MX switches
Desk LampNo description available
Standing DeskHeight-adjustable standing desk

Both empty strings and NULLs are now replaced with the fallback text.

tip

The pattern COALESCE(NULLIF(column, ''), default) is essential for handling messy data where both empty strings and NULLs represent "no value." It normalizes both cases into a single fallback.

NULLIF for Conditional Suppression

Suppress a value that should not be displayed:

-- Show discount only if it's greater than 0
SELECT
order_id,
discount_pct,
NULLIF(discount_pct, 0) AS meaningful_discount
FROM orders;
order_iddiscount_pctmeaningful_discount
100110.0010.00
1002NULLNULL
10035.005.00
1004NULLNULL
10050.00NULL

Order 1005 had discount_pct = 0.00, which NULLIF converts to NULL, distinguishing "no discount applied" from "discount unknown."

IFNULL and ISNULL: Database-Specific Alternatives

COALESCE is the SQL standard and works everywhere, but several databases offer their own two-argument shortcuts.

MySQL: IFNULL

-- MySQL: IFNULL(expression, default_value)
SELECT
first_name,
IFNULL(phone, 'No phone') AS phone,
IFNULL(loyalty_points, 0) AS points
FROM customers;
note

IFNULL(a, b) is equivalent to COALESCE(a, b) but accepts exactly two arguments.

SQL Server: ISNULL

-- SQL Server: ISNULL(expression, default_value)
SELECT
first_name,
ISNULL(phone, 'No phone') AS phone,
ISNULL(loyalty_points, 0) AS points
FROM customers;

Differences from COALESCE

FeatureCOALESCEIFNULL (MySQL)ISNULL (SQL Server)
SQL StandardYesNoNo
Number of argumentsUnlimitedExactly 2Exactly 2
Return typeDetermined by all argumentsDetermined by first argumentDetermined by first argument
Available inAll databasesMySQL onlySQL Server only
warning

ISNULL in SQL Server has a subtle type behavior that differs from COALESCE. It uses the data type of the first argument for the return type, which can silently truncate data:

-- SQL Server: ISNULL truncates to the first argument's type
DECLARE @short VARCHAR(5) = NULL;
SELECT ISNULL(@short, 'This is a long string');
-- Result: 'This ' (truncated to 5 characters!)

SELECT COALESCE(@short, 'This is a long string');
-- Result: 'This is a long string' (full string)

Always prefer COALESCE over ISNULL in SQL Server to avoid this truncation trap.

Oracle: NVL

Oracle's equivalent is NVL:

-- Oracle: NVL(expression, default_value)
SELECT
first_name,
NVL(phone, 'No phone') AS phone,
NVL(loyalty_points, 0) AS points
FROM customers;

Oracle also provides NVL2, which returns one value if the expression is not NULL and a different value if it is:

-- Oracle: NVL2(expression, value_if_not_null, value_if_null)
SELECT
first_name,
NVL2(email, 'Has email', 'No email') AS email_status
FROM customers;

Summary: Which Function to Use

DatabaseTwo-Argument ShortcutRecommended
PostgreSQLNone (use COALESCE)COALESCE
MySQLIFNULLCOALESCE (standard, more flexible)
SQL ServerISNULLCOALESCE (avoids truncation)
OracleNVLCOALESCE (standard) or NVL
SQLiteIFNULLCOALESCE
tip

Use COALESCE everywhere. It is the SQL standard, works in every database, supports unlimited arguments, and avoids the truncation issues of ISNULL. The database-specific functions exist for historical reasons, but there is no advantage to using them in new code.

IS NULL and IS NOT NULL: The Only Safe NULL Comparisons

You cannot use = or != to compare with NULL. You must use IS NULL and IS NOT NULL.

-- Wrong: these conditions NEVER match NULL rows
SELECT * FROM customers WHERE phone = NULL; -- 0 rows (always)
SELECT * FROM customers WHERE phone != NULL; -- 0 rows (always)

-- Correct
SELECT * FROM customers WHERE phone IS NULL; -- Bob, Eve
SELECT * FROM customers WHERE phone IS NOT NULL; -- Alice, Carol, Dave

Common Pattern: Finding Incomplete Records

-- Customers missing any contact information
SELECT
customer_id,
first_name,
email,
phone
FROM customers
WHERE email IS NULL
OR phone IS NULL;
customer_idfirst_nameemailphone
2Bobbob@example.comNULL
3CarolNULL555-0103
5EveNULLNULL

Common Pattern: Finding Unshipped Orders

SELECT order_id, order_date, ship_date
FROM orders
WHERE ship_date IS NULL;
order_idorder_dateship_date
10032024-06-05NULL
10042024-06-10NULL
10052024-06-14NULL

NULL-Safe Strategies

Strategy 1: Default NULLs at Query Time

Use COALESCE in your SELECT to present clean data without modifying the underlying table:

SELECT
product_name,
COALESCE(sale_price, price) AS display_price,
COALESCE(NULLIF(description, ''), 'No description') AS display_description,
COALESCE(stock_qty, 0) AS display_stock
FROM products;

Strategy 2: Prevent NULLs at the Schema Level

The best NULL strategy is often to prevent them from entering the database in the first place:

CREATE TABLE customers (
customer_id INT PRIMARY KEY,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
email VARCHAR(100), -- nullable: email is optional
loyalty_points INT NOT NULL DEFAULT 0, -- never NULL: use 0 instead
city VARCHAR(50) NOT NULL DEFAULT 'Unknown', -- never NULL
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

Use NOT NULL DEFAULT value for columns where "no data" has a meaningful zero or default. Leave columns nullable only when NULL genuinely means "unknown" or "not applicable."

Strategy 3: Normalize NULLs on Insert/Update

Clean data as it enters the system:

-- Normalize empty strings to NULL on insert
INSERT INTO customers (customer_id, first_name, last_name, email, phone)
VALUES (6, 'Frank', 'Lee', NULLIF(TRIM(''), ''), NULLIF(TRIM(' '), ''));
-- Both empty/whitespace-only strings become NULL

-- Clean existing data
UPDATE products
SET description = NULLIF(TRIM(description), '')
WHERE description = '' OR description = ' ';

Strategy 4: COALESCE in JOINs

NULL foreign keys do not match in standard joins. Handle them explicitly:

-- This misses customers with NULL city values in a join
SELECT c.first_name, r.region_name
FROM customers c
JOIN regions r ON c.city = r.city;
-- Customers with city = NULL are excluded

-- Include them with COALESCE
SELECT c.first_name, COALESCE(r.region_name, 'Unassigned') AS region
FROM customers c
LEFT JOIN regions r ON c.city = r.city;

Strategy 5: NULL-Safe Equality (Database-Specific)

Some databases offer operators that treat NULL = NULL as TRUE:

-- PostgreSQL: IS NOT DISTINCT FROM
SELECT * FROM customers c1
JOIN customers c2 ON c1.city IS NOT DISTINCT FROM c2.city;
-- Matches rows even when both cities are NULL

-- MySQL: <=> (NULL-safe equals)
SELECT * FROM customers c1
JOIN customers c2 ON c1.city <=> c2.city;
-- Same behavior

-- Standard SQL equivalent (all databases)
SELECT * FROM customers c1
JOIN customers c2 ON c1.city = c2.city
OR (c1.city IS NULL AND c2.city IS NULL);

COALESCE with CASE: Powerful Combinations

COALESCE and CASE work together naturally:

-- Complex fallback logic
SELECT
order_id,
COALESCE(
CASE WHEN ship_date IS NOT NULL THEN 'Shipped on ' || ship_date::TEXT END,
CASE WHEN notes IS NOT NULL AND notes != '' THEN 'Note: ' || notes END,
'No updates available'
) AS status_info
FROM orders;
order_idstatus_info
1001Shipped on 2024-01-18
1002Shipped on 2024-03-25
1003Note: Fragile items
1004No updates available
1005No updates available
info

This pattern uses CASE expressions that return NULL when their condition fails (no ELSE clause), and COALESCE picks the first non-NULL result. It creates a priority chain: show shipping info if available, then notes, then a generic message.

Common Mistakes

Mistake 1: Using = Instead of IS NULL

-- Bug: returns 0 rows even when NULLs exist
SELECT * FROM customers WHERE email = NULL;

-- Correct
SELECT * FROM customers WHERE email IS NULL;

This is the most fundamental NULL mistake and the first one every developer should memorize.

Mistake 2: NOT IN with NULLs

NOT IN silently fails when the subquery contains NULL:

-- Suppose the subquery returns (1, 2, NULL)
SELECT * FROM customers
WHERE customer_id NOT IN (1, 2, NULL);
-- Returns 0 rows! Always!

Why? customer_id NOT IN (1, 2, NULL) means customer_id != 1 AND customer_id != 2 AND customer_id != NULL. The last condition is always UNKNOWN, making the entire AND chain UNKNOWN.

Fix: Use NOT EXISTS or filter NULLs from the subquery:

-- Safe: NOT EXISTS handles NULLs correctly
SELECT * FROM customers c
WHERE NOT EXISTS (
SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id
);

-- Also safe: filter NULLs out of NOT IN
SELECT * FROM customers
WHERE customer_id NOT IN (
SELECT customer_id FROM orders WHERE customer_id IS NOT NULL
);
danger

NOT IN with a subquery that can return NULL is one of the most dangerous patterns in SQL. The query runs without error but returns wrong results. Always prefer NOT EXISTS over NOT IN when checking for absence.

Mistake 3: Forgetting That Aggregates Skip NULLs

-- How many customers have loyalty points?
SELECT
COUNT(*) AS total_customers, -- 5
COUNT(loyalty_points) AS with_points, -- 3 (skips NULLs)
AVG(loyalty_points) AS avg_points, -- 116.67 (350/3, not 350/5)
SUM(loyalty_points) AS total_points -- 350
FROM customers;
total_customerswith_pointsavg_pointstotal_points
53116.67350

If NULL should be treated as zero:

SELECT
COUNT(*) AS total_customers,
AVG(COALESCE(loyalty_points, 0)) AS avg_points, -- 70.00 (350/5)
SUM(COALESCE(loyalty_points, 0)) AS total_points -- 350
FROM customers;
total_customersavg_pointstotal_points
570.00350

Mistake 4: ORDER BY with NULLs

NULLs sort to the beginning or end depending on the database:

DatabaseNULLs in ASCNULLs in DESC
PostgreSQLLastFirst
MySQLFirstLast
SQL ServerFirstLast
OracleLastFirst
SELECT first_name, loyalty_points
FROM customers
ORDER BY loyalty_points ASC;
-- Where do Bob (NULL) and Eve (NULL) appear? Depends on your database!

Control NULL sort position explicitly:

-- PostgreSQL / Oracle: NULLS FIRST or NULLS LAST
SELECT first_name, loyalty_points
FROM customers
ORDER BY loyalty_points ASC NULLS LAST;

-- All databases: use COALESCE to control sort
SELECT first_name, loyalty_points
FROM customers
ORDER BY COALESCE(loyalty_points, -1) ASC;
-- NULLs treated as -1, sorting them before 0

Mistake 5: Confusing Empty String with NULL

-- These are DIFFERENT values
SELECT
'' IS NULL AS empty_is_null, -- FALSE (in most databases)
NULL IS NULL AS null_is_null, -- TRUE
LENGTH('') AS empty_length, -- 0
LENGTH(NULL) AS null_length; -- NULL
empty_is_nullnull_is_nullempty_lengthnull_length
FALSETRUE0NULL
info

Oracle is the exception: In Oracle, an empty string '' IS treated as NULL. This is Oracle-specific behavior that differs from every other database.

-- Oracle only:
SELECT '' IS NULL FROM DUAL; -- Result: TRUE
SELECT LENGTH('') FROM DUAL; -- Result: NULL

If you are writing cross-database code, never rely on empty string behavior. Use NULLIF(column, '') to normalize both cases.

Mistake 6: COALESCE with Mismatched Types

All arguments to COALESCE should be the same type or compatible types:

-- Error in strict databases: mixing integer and string
SELECT COALESCE(loyalty_points, 'N/A') FROM customers;
-- ERROR: COALESCE types integer and text cannot be matched

Fix: Cast to a common type:

SELECT COALESCE(CAST(loyalty_points AS VARCHAR), 'N/A') FROM customers;
-- Or for display: keep the number numeric, handle NULL separately
SELECT COALESCE(loyalty_points, 0) FROM customers;

Comprehensive NULL Handling Cheat Sheet

TaskSolutionExample
Replace NULL with defaultCOALESCE(col, default)COALESCE(phone, 'N/A')
Multi-level fallbackCOALESCE(a, b, c)COALESCE(mobile, home, work)
Safe divisiona / NULLIF(b, 0)price / NULLIF(qty, 0)
Empty string as NULLNULLIF(col, '')NULLIF(description, '')
Clean + defaultCOALESCE(NULLIF(col,''), default)COALESCE(NULLIF(notes,''), 'None')
Check for NULLcol IS NULLWHERE email IS NULL
Check for non-NULLcol IS NOT NULLWHERE phone IS NOT NULL
NULL-safe compareIS NOT DISTINCT FROM (PG)a IS NOT DISTINCT FROM b
Count including NULLsCOUNT(*)COUNT(*) counts all rows
Count excluding NULLsCOUNT(column)COUNT(email) skips NULLs
AVG treating NULL as 0AVG(COALESCE(col, 0))AVG(COALESCE(points, 0))
Sort NULLs lastORDER BY col NULLS LASTPostgreSQL / Oracle
Sort NULLs via COALESCEORDER BY COALESCE(col, val)All databases

Summary

SQL COALESCE is the standard function for replacing NULL values with meaningful defaults. Together with NULLIF, IS NULL, and database-specific functions like IFNULL and ISNULL, it forms the toolkit for handling one of SQL's most challenging concepts.

Key takeaways:

  • NULL means unknown, not zero, not empty string. It behaves unlike any other value: comparisons with NULL return UNKNOWN, arithmetic with NULL returns NULL, and most functions receiving NULL return NULL.
  • COALESCE(a, b, c, ...) returns the first non-NULL argument. It is the SQL standard, works in every database, and should be your go-to function for NULL replacement. Use it for default values, fallback chains, safe calculations, and clean display output.
  • NULLIF(a, b) returns NULL when a = b. Its primary use is preventing division by zero (x / NULLIF(divisor, 0)) and converting empty strings to NULL (NULLIF(column, '')).
  • IFNULL (MySQL/SQLite), ISNULL (SQL Server), and NVL (Oracle) are database-specific two-argument shortcuts. Prefer COALESCE for portability and to avoid SQL Server's ISNULL truncation behavior.
  • Use IS NULL and IS NOT NULL for comparisons, never = NULL or != NULL.
  • Beware of NOT IN with NULLs, which silently returns no rows. Use NOT EXISTS instead.
  • Aggregates skip NULLs silently. COUNT(column) counts only non-NULL values. AVG divides by the count of non-NULL values only. Use COALESCE to include NULLs in aggregations when appropriate.
  • Prevent NULLs at the schema level with NOT NULL DEFAULT value for columns where NULL is not a meaningful state.
  • The pattern COALESCE(NULLIF(column, ''), 'default') handles both empty strings and NULLs, normalizing messy imported data into clean, consistent output.

Handle NULL deliberately and explicitly. Every column you query should have a conscious strategy: either prevent NULL at the schema level, replace it with COALESCE at query time, or accept it as a valid "unknown" state with appropriate IS NULL checks.