SQL • QUERYING DATA

IS NULL / IS NOT NULL — Handle NULLs with IS NULL / IS NOT NULL

Master three-valued logic to correctly filter missing data in relational queries.

Historical Context & Motivation

The concept of NULL in relational databases traces back to the foundational work of E.F. Codd, who recognized that real-world data is frequently incomplete, unknown, or inapplicable. Unlike programming languages that typically use sentinel values such as zero, empty strings, or dedicated null pointers, Codd envisioned a special marker within the relational model itself to represent the absence of a value rather than a value of zero or an empty string. This distinction carries profound implications for how comparisons, aggregations, and logical expressions behave in SQL, and it necessitated entirely new operators — IS NULL and IS NOT NULL — to test for this special state.

1970
Codd's Relational Model
E.F. Codd publishes "A Relational Model of Data for Large Shared Data Banks," introducing the concept of NULL as a marker for missing or inapplicable information in relational tuples.
1979
Three-Valued Logic Formalized
Codd formally proposes three-valued logic (TRUE, FALSE, UNKNOWN) for SQL predicates involving NULLs, departing from classical Boolean logic used in most programming paradigms.
1986
SQL-86 Standard
The first ANSI SQL standard (SQL-86) codifies IS NULL and IS NOT NULL as the proper predicates for testing NULL values, explicitly prohibiting the use of = or <> for NULL comparisons.
1992
SQL-92 Enhancements
SQL-92 introduces COALESCE and NULLIF functions, expanding the toolkit for NULL handling while reinforcing that IS NULL / IS NOT NULL remain the canonical test predicates.
2023
Modern SQL and NULL Safety
Contemporary databases (PostgreSQL, MySQL 8, SQL Server) implement IS DISTINCT FROM and IS NOT DISTINCT FROM as NULL-safe equality operators, but IS NULL and IS NOT NULL remain the foundational predicates.

The central question this lesson addresses is deceptively simple: if NULL is not a value, how do you test for it? Why does WHERE column = NULL always return zero rows, and what must you write instead? Understanding the answer requires grappling with three-valued logic — the subtle but critical departure from the Boolean logic you encounter in general-purpose programming languages.

Core Principles & Definitions

Before writing queries that handle NULLs correctly, you need to internalize several foundational principles that govern how SQL engines treat this special marker. These principles explain why NULL behaves differently from every other token in the language and why dedicated predicates are required.

1

NULL Is Not a Value

NULL represents the absence of data — it is neither zero, nor an empty string, nor FALSE. It is a marker indicating that a value is unknown, missing, or inapplicable for a given column in a given row.
2

Three-Valued Logic (3VL)

Any comparison involving NULL yields UNKNOWN, not TRUE or FALSE. SQL's WHERE clause only passes rows where the predicate evaluates to TRUE, so UNKNOWN rows are filtered out — just like FALSE.
3

IS NULL — The Positive Test

The predicate column IS NULL evaluates to TRUE when the column contains NULL, and FALSE otherwise. It is the only correct way to check for the presence of NULL.
4

IS NOT NULL — The Negative Test

The predicate column IS NOT NULL evaluates to TRUE when the column contains any actual value (including zero, empty string, or spaces), and FALSE when it is NULL.
5

NULL Propagation

Arithmetic and string operations with NULL produce NULL: 5 + NULL → NULL, 'hello' || NULL → NULL. NULLs are "contagious" through expressions.
KEY TAKEAWAY
Think of NULL like a sealed, opaque envelope. You cannot compare the contents of a sealed envelope to the number 5, or to the word "hello," or even to another sealed envelope — because you simply don't know what is inside. The only meaningful question you can ask is: "Is this envelope sealed?" That is exactly what IS NULL does — it checks whether the envelope is sealed, without trying to peek inside.

Visual Explanation — Three-Valued Logic Flow

The diagram below illustrates how SQL's WHERE clause evaluates predicates under three-valued logic. When a column value is compared using standard operators (=, <>, <, >, etc.), the result can be TRUE, FALSE, or UNKNOWN. Only rows that evaluate to TRUE pass the filter. This is precisely why WHERE col = NULL silently drops every row — the comparison always yields UNKNOWN, which the WHERE clause treats identically to FALSE.

The flowchart shows two paths when a WHERE predicate involves a NULL column. Using standard comparison operators (=, <>) always yields UNKNOWN, causing the row to be excluded. Using IS NULL or IS NOT NULL correctly produces TRUE or FALSE, allowing proper filtering.

Notice that the diagram's right branch — the one representing col = NULL — leads directly to row exclusion with no opportunity for the row to pass. This is the single most common bug in SQL queries written by newcomers to the language. The left branch, using IS NULL or IS NOT NULL, properly evaluates to a definite Boolean result, allowing the WHERE clause to include or exclude the row as intended.

How Three-Valued Logic Works

SQL implements Kleene's three-valued logic (3VL), which extends classical Boolean logic by adding a third truth value: UNKNOWN. Every predicate in SQL evaluates to one of three states: TRUE (T), FALSE (F), or UNKNOWN (U). The behavior of the logical connectives AND, OR, and NOT under 3VL determines how compound predicates involving NULLs behave. Understanding these truth tables is essential for writing correct WHERE clauses that combine NULL-aware predicates with other conditions.

AND Truth Table (3VL)

AND yields TRUE only when both operands are TRUE. FALSE dominates UNKNOWN.
ABA AND B
TRUETRUETRUE
TRUEUNKNOWNUNKNOWN
TRUEFALSEFALSE
UNKNOWNUNKNOWNUNKNOWN
FALSEUNKNOWNFALSE
FALSEFALSEFALSE

OR Truth Table (3VL)

OR yields FALSE only when both operands are FALSE. TRUE dominates UNKNOWN.
ABA OR B
TRUEUNKNOWNTRUE
UNKNOWNUNKNOWNUNKNOWN
FALSEUNKNOWNUNKNOWN
FALSEFALSEFALSE

NOT Truth Table (3VL)

NOT UNKNOWN remains UNKNOWN — negation does not resolve uncertainty.
ANOT A
TRUEFALSE
FALSETRUE
UNKNOWNUNKNOWN
⚠️ Critical Implication
Because NOT (col = NULL) evaluates as NOT UNKNOWN which is still UNKNOWN, writing WHERE NOT (col = NULL) also returns zero rows. You cannot work around the problem by wrapping the comparison in NOT — you must use IS NOT NULL.

NULL Behavior Across SQL Contexts

NULLs do not only affect WHERE clauses — their behavior permeates virtually every SQL operation. The following diagram and table catalog these behaviors systematically, helping you anticipate where NULLs can produce surprising results.

This behavior map shows how NULL propagates through four major SQL contexts: comparisons, aggregates, arithmetic, and sorting. All roads lead back to the same solution — use IS NULL and IS NOT NULL for explicit NULL detection.
Summary of NULL behaviors across major SQL contexts
SQL ContextNULL BehaviorImplication
WHERE / HAVINGUNKNOWN treated as FALSERows with NULL in filtered column silently disappear unless IS NULL / IS NOT NULL is used
GROUP BYAll NULLs grouped togetherDespite NULL ≠ NULL in comparisons, GROUP BY treats all NULLs as one group
DISTINCTNULLs considered duplicatesSELECT DISTINCT collapses multiple NULL rows into one NULL
COUNT(col)Skips NULLsCOUNT(*) counts all rows; COUNT(col) counts only non-NULL values — a common source of off-by-one-style errors
UNIQUE constraintMultiple NULLs allowed (most vendors)Since NULL ≠ NULL, multiple NULLs do not violate uniqueness in PostgreSQL, MySQL, and Oracle (but SQL Server differs)
IN / NOT INNOT IN with NULLs returns empty setIf the subquery or list contains any NULL, NOT IN yields UNKNOWN for every row — a notorious pitfall

Worked Example — Filtering Incomplete Customer Records

Consider a customers table with columns id, name, email, and phone. Some customers registered without providing a phone number or email. The task is to find customers who are missing contact information and then to identify customers who have complete profiles.

Sample customers table — NULL cells highlighted in amber
idnameemailphone
1Alicealice@ex.com555-0101
2Bobbob@ex.comNULL
3CarolNULL555-0303
4DaveNULLNULL
5Eveeve@ex.com555-0505
Finding and Filtering NULL Values
1
Step 1 — Identify customers with no phone numberWrite a query that selects customers whose phone column is NULL. The predicate must use IS NULL because WHERE phone = NULL would return zero rows. SELECT name, email FROM customers WHERE phone IS NULL;
Returns Bob (id 2) and Dave (id 4).
2
Step 2 — Identify customers missing any contact informationUse OR to combine two IS NULL checks. This retrieves rows where either email or phone is missing. SELECT name FROM customers WHERE email IS NULL OR phone IS NULL;
Returns Bob (no phone), Carol (no email), and Dave (no email, no phone).
3
Step 3 — Find customers with complete profilesUse AND to combine two IS NOT NULL checks. Only rows where both fields contain actual values will pass. SELECT name, email, phone FROM customers WHERE email IS NOT NULL AND phone IS NOT NULL;
Returns Alice (id 1) and Eve (id 5) — the only two customers with both email and phone populated.
4
Step 4 — Count completeness using COALESCEUse COALESCE to substitute a default value when NULL is encountered, useful for reporting. SELECT name, COALESCE(email, 'N/A') AS email, COALESCE(phone, 'N/A') AS phone FROM customers;
All 5 rows returned. NULL values appear as 'N/A' in the output, making reports more readable.
5
Step 5 — Compare COUNT(*) vs COUNT(column)Demonstrate the difference between counting all rows and counting non-NULL values. SELECT COUNT(*) AS total_rows, COUNT(email) AS has_email, COUNT(phone) AS has_phone FROM customers;
Returns total_rows = 5, has_email = 3 (Alice, Bob, Eve), has_phone = 3 (Alice, Carol, Eve). COUNT(column) silently skips NULLs.

Common Pitfalls and Defensive Patterns

NULL-related bugs are among the most insidious in SQL because they tend to produce silently wrong results rather than errors. A query that uses WHERE col = NULL will execute without any syntax error — it simply returns an empty result set, leading developers to believe the table has no matching data when in fact the predicate is logically malformed. The table below catalogs the most common pitfalls and their correct alternatives.

Common NULL pitfalls with their silent failure modes and correct alternatives
PitfallWhat HappensCorrect Pattern
WHERE col = NULLAlways yields UNKNOWN; returns 0 rowsWHERE col IS NULL
WHERE col <> NULLAlways yields UNKNOWN; returns 0 rowsWHERE col IS NOT NULL
WHERE col NOT IN (SELECT ...)Returns empty set if subquery has any NULLWHERE col NOT IN (SELECT ... WHERE x IS NOT NULL)
WHERE col = ''Finds empty strings, not NULLs — they are differentWHERE col IS NULL OR col = ''
Using AVG without awarenessAVG skips NULLs — denominator only includes non-NULL rowsAVG(COALESCE(col, 0)) if zeros are appropriate
Joining on nullable columnsNULL = NULL is UNKNOWN; rows with NULL keys never matchUse COALESCE on join keys or IS NOT DISTINCT FROM
🛡️ DEFENSIVE PROGRAMMING PATTERN
When writing any SQL query, mentally check each column referenced in WHERE, JOIN ON, or aggregate functions: "Can this column contain NULL?" If the answer is yes — or even maybe — add explicit NULL handling. This practice is analogous to checking for null pointers in Java or handling Optional in Rust; it is a form of defensive programming that prevents subtle data integrity bugs from reaching production.

Connection to Advanced NULL Handling

While IS NULL and IS NOT NULL are the foundational predicates for NULL detection, the SQL standard and modern database engines offer more sophisticated tools for NULL handling. Understanding these advanced constructs positions you to write more concise, more robust, and more portable queries. The table below maps each foundational concept to its advanced counterpart.

Mapping foundational NULL predicates to their advanced counterparts
Foundational ConceptAdvanced ExtensionDescription
IS NULLCOALESCE(a, b, ...)Returns the first non-NULL argument. Replaces common CASE WHEN ... IS NULL patterns with a concise function call.
IS NOT NULLNULLIF(a, b)Returns NULL if a equals b, otherwise returns a. Useful for converting sentinel values (e.g., 0 or '') back to NULL for correct aggregation.
NULL = NULL → UNKNOWNIS [NOT] DISTINCT FROMNULL-safe equality operator (SQL:2003). NULL IS NOT DISTINCT FROM NULL → TRUE. Eliminates the need for verbose IS NULL OR a = b patterns in JOIN conditions.
CASE WHEN x IS NULLIFNULL(a, b)Vendor-specific shorthand (MySQL, SQLite) equivalent to COALESCE with two arguments.
NOT IN pitfallNOT EXISTSEXISTS uses semi-join semantics that handle NULLs correctly. Always prefer NOT EXISTS over NOT IN when the subquery column is nullable.

As you advance to topics such as window functions, common table expressions (CTEs), and recursive queries, NULL handling becomes even more critical. Window functions like LAG and LEAD produce NULLs at partition boundaries, OUTER JOINs introduce NULLs for non-matching rows, and recursive CTEs must guard against NULL propagation to avoid infinite loops or silent data loss. The skills you build with IS NULL and IS NOT NULL form the foundation for all of these advanced patterns.

Practice Problems

The following five problems escalate in difficulty, from conceptual understanding to critical analysis. Work through each one, writing out the SQL before checking the answer. Assume standard ANSI SQL behavior unless otherwise specified.

PROBLEM 1CONCEPTUAL
Explain why the following query always returns zero rows, regardless of the data in the table: SELECT * FROM orders WHERE discount = NULL;
PROBLEM 2BASIC CALCULATION
Given a table employees(id, name, manager_id) where the CEO has a NULL manager_id, write a query to find the CEO's name.
PROBLEM 3INTERMEDIATE
A products table has columns (id, name, price, weight). Some products have NULL weight. Write a single query that returns all products, showing the weight where available and 'Unknown' where it is NULL, and sort so that products with known weights appear first (ascending), followed by unknowns.
PROBLEM 4APPLIED
A data quality report requires you to count, for each column in a survey_responses(id, q1, q2, q3, q4, q5) table, how many responses are NULL (unanswered) versus non-NULL. Write a query that produces a single result row with columns q1_missing, q1_answered, q2_missing, q2_answered, ... and so on for all five questions.
PROBLEM 5CRITICAL THINKING
A colleague writes the following query to find departments that have no employees assigned to a specific project: SELECT dept_name FROM departments WHERE dept_id NOT IN (SELECT dept_id FROM project_assignments WHERE project_id = 42); The query returns zero rows even though you know some departments have no one on project 42. Diagnose the bug and provide a corrected version. Explain why your fix works.

Lesson Summary

NULL is a special marker in SQL representing the absence of a value — it is not zero, not an empty string, and not FALSE. Because SQL uses three-valued logic (TRUE, FALSE, UNKNOWN), any comparison with NULL using standard operators (=, <>, <, >) evaluates to UNKNOWN, which the WHERE clause treats identically to FALSE. This is why IS NULL and IS NOT NULL exist: they are the only predicates that can properly evaluate to TRUE or FALSE when testing for the NULL marker.

Key behaviors to remember: NULL propagates through arithmetic (5 + NULL → NULL), aggregate functions like COUNT(col) skip NULLs while COUNT(*) includes all rows, and NOT IN with NULLs in the subquery returns an empty set. For advanced NULL handling, use COALESCE to supply default values, NULLIF to convert sentinel values back to NULL, and NOT EXISTS instead of NOT IN when nullable columns are involved.

Varsity Tutors • SQL • IS NULL / IS NOT NULL — Handle NULLs with IS NULL / IS NOT NULL