SQL NULL is not a value — it's a marker for missing or unknown data, and it breaks every assumption you have about how comparisons and logic work. The core problem: NULL = NULL evaluates to unknown, not true, so WHERE bonus = NULL returns zero rows even when NULLs exist.
★
Imagine you're filling out a form and you leave the 'middle name' box completely blank — not a dash, not a space, just nothing at all.
This three-valued logic (true, false, unknown) infects WHERE clauses, JOIN conditions, and CHECK constraints, silently dropping rows from results. You must use IS NULL, IS DISTINCT FROM, or functions like COALESCE to handle it explicitly.
COALESCE replaces NULL with a fallback value — COALESCE(bonus, 0) turns missing bonuses into zeros for calculations. NULLIF does the reverse: NULLIF(column, 0) converts a known value to NULL, useful for avoiding division-by-zero or excluding defaults from aggregates. Aggregate functions like AVG() and SUM() ignore NULLs entirely, which can skew results — COUNT(*) counts rows, but COUNT(column) counts only non-NULL values, a distinction that burns teams in reporting.
The real cost is performance: NULLs in indexed columns force databases to store extra bitmap pages (PostgreSQL) or skip index-only scans (SQL Server), because the index can't prove a NULL doesn't exist. A WHERE bonus IS NULL query on a 10M-row table might scan the whole index instead of seeking.
In practice, this 'nullability tax' means you should avoid nullable columns in primary keys, unique constraints, and join columns unless you're willing to pay the query-plan penalty. Tools like dbt and ORMs (ActiveRecord, Entity Framework) often default columns to nullable, silently degrading performance until you add NOT NULL constraints.
Plain-English First
Imagine you're filling out a form and you leave the 'middle name' box completely blank — not a dash, not a space, just nothing at all. That blank box is NULL in SQL. It doesn't mean zero, it doesn't mean empty string, it means 'we simply don't know or don't have this information.' Because NULL represents the absence of a value, normal math and comparisons don't work on it the same way — you can't ask 'is nothing equal to nothing?' and get a sensible yes or no.
Every real-world database has missing data. A customer who didn't provide a phone number. An order with no shipping date yet because it hasn't shipped. An employee whose bonus hasn't been decided. These gaps are everywhere, and SQL has a special way of representing them — NULL. If you don't understand NULL, your queries will silently return wrong answers without throwing a single error, which is one of the most dangerous bugs a database can have.
The tricky part is that NULL breaks the rules you're used to. In normal life, if you ask 'is blank equal to blank?' you'd say yes. SQL says 'I don't know' — and that 'I don't know' answer is what causes queries to behave unexpectedly. Filters exclude rows you expected to include, calculations return NULL when you expected a number, and JOINs quietly drop data. SQL gives you specific tools — IS NULL, IS NOT NULL, COALESCE, and NULLIF — to handle these gaps safely and deliberately.
By the end of this article you'll understand what NULL actually means (and what it doesn't), why you can't use = NULL in a WHERE clause, how to filter and replace NULLs with confidence, and how to avoid the silent bugs that trip up even experienced developers. You'll be writing NULL-safe SQL from scratch.
What NULL Actually Is — and Why = NULL Never Works
NULL isn't a value — it's the absence of a value. Think of it like a sealed envelope. You don't know what's inside. If someone hands you two sealed envelopes and asks 'are these the same?', the honest answer is 'I have no idea' — not yes, not no. SQL agrees. Comparing anything to NULL using = produces a third logical state called UNKNOWN, which is neither TRUE nor FALSE.
This is why WHERE salary = NULL will never return any rows, even if the salary column genuinely contains NULLs. SQL evaluates the condition, gets UNKNOWN for every row, and excludes them all. WHERE only keeps rows where the condition is TRUE.
SQL gives you a dedicated operator for NULL checks: IS NULL and IS NOT NULL. These are the only correct ways to test whether a value is absent. They return TRUE or FALSE — no UNKNOWN — which is exactly what WHERE needs to filter rows correctly.
This also means NULL = NULL is NOT true in SQL. Two unknown things are not automatically equal. This catches beginners off guard constantly, especially when writing JOIN conditions.
null_basics_check.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
-- Create a simple employees table to demonstrate NULL behaviorCREATETABLEemployees (
employee_id INTPRIMARYKEY,
full_name VARCHAR(100) NOTNULL,
department VARCHAR(50),
phone_number VARCHAR(20), -- nullable: not every employee has provided one
annual_bonus DECIMAL(10,2) -- nullable: bonus not yet decided for some staff
);
-- Insert sample rows — some have NULLs, some don'tINSERTINTO employees VALUES (1, 'Alice Mercer', 'Engineering', '555-0101', 4500.00);
INSERTINTO employees VALUES (2, 'Ben Okafor', 'Marketing', NULL, 3200.00);
INSERTINTO employees VALUES (3, 'Clara Huang', 'Engineering', '555-0303', NULL);
INSERTINTO employees VALUES (4, 'David Nkosi', 'HR', NULL, NULL);
INSERTINTO employees VALUES (5, 'Eva Johansson', 'Marketing', '555-0505', 2800.00);
-- ❌ WRONG: This returns 0 rows even though NULLs exist in phone_number-- WHERE evaluates phone_number = NULL as UNKNOWN for every rowSELECT full_name
FROM employees
WHERE phone_number = NULL;
-- Output: (0 rows) — silent bug!-- ✅ CORRECT: Use IS NULL to find employees without a phone numberSELECT full_name
FROM employees
WHERE phone_number ISNULL;
-- Output: Ben Okafor, David Nkosi-- ✅ CORRECT: Use IS NOT NULL to find employees who DO have a phone numberSELECT full_name, phone_number
FROM employees
WHERE phone_number ISNOTNULL;
-- Output: Alice Mercer | 555-0101-- Clara Huang | 555-0303-- Eva Johansson| 555-0505
Output
-- phone_number = NULL query:
(0 rows returned)
-- IS NULL query:
full_name
----------
Ben Okafor
David Nkosi
-- IS NOT NULL query:
full_name | phone_number
----------------|-------------
Alice Mercer | 555-0101
Clara Huang | 555-0303
Eva Johansson | 555-0505
Watch Out:
Never use = NULL or != NULL in a WHERE clause. They will silently return zero rows without any error message. SQL won't warn you — it'll just quietly drop your data. Always use IS NULL or IS NOT NULL.
Production Insight
The three-valued logic of SQL (TRUE, FALSE, UNKNOWN) is the source of countless production bugs.
NULL in a WHERE condition produces UNKNOWN, which is treated as FALSE — the row is excluded.
CONCAT with NULL produces NULL in some databases (PostgreSQL) and skips NULL in others (MySQL) — use COALESCE before CONCAT.
Key Takeaway
NULL = NULL is UNKNOWN, not TRUE — never use = or != to test for NULL.
Any expression involving NULL produces NULL — the result is unknown, not zero or false.
IS NULL and IS NOT NULL are the only correct operators for NULL tests.
thecodeforge.io
SQL NULL Handling: 340 Employees Missing Bonuses
Sql Null Handling
COALESCE — Replacing NULL With a Sensible Default
Now that you can detect NULLs, the next question is: what do you do with them? Often you want to replace a NULL with a default value so your reports and application logic get something useful instead of a blank.
That's exactly what COALESCE does. You give it a list of values and it hands back the first one that isn't NULL. Think of it like a backup plan — 'give me the first option, but if that's blank, try the second, and if that's blank too, try the third.'
Coalesce is standard SQL and works in MySQL, PostgreSQL, SQL Server, SQLite, and Oracle. It's one of the most-used NULL-handling functions in production databases.
A key use case is calculated columns. If annual_bonus is NULL and you add it to a base salary, the entire result becomes NULL — SQL says 'I can't add a known number to an unknown number and give you a real answer.' COALESCE lets you substitute zero (or any default) so your arithmetic stays intact.
COALESCE can take more than two arguments. COALESCE(a, b, c, d) returns the first non-NULL across all four — really useful when you have primary, secondary, and fallback data sources.
coalesce_bonus_report.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
-- COALESCE(value_to_check, fallback_if_null)-- Returns the first argument that is NOT NULL-- Use case 1: Display a friendly label instead of NULL in a reportSELECT
full_name,
COALESCE(phone_number, 'No phone on file') AS contact_number
FROM employees;
-- Use case 2: Safe arithmetic — treat a missing bonus as 0-- Without COALESCE, NULL bonus would make the entire total NULLSELECT
full_name,
80000AS base_salary,
COALESCE(annual_bonus, 0) AS bonus, -- swap NULL for 080000 + COALESCE(annual_bonus, 0) AS total_compensation
FROM employees;
-- Use case 3: Multiple fallbacks — try preferred_email, then work_email, then a default-- (Imaginary columns to show the pattern)-- COALESCE(preferred_email, work_email, 'no-email@company.com')-- Use case 4: COALESCE in a WHERE clause to filter on computed valuesSELECT full_name
FROM employees
WHERECOALESCE(annual_bonus, 0) < 3000; -- treats NULL bonus as 0 for comparison
-- Employees with bonus under 3000 (NULLs treated as 0):
full_name
----------
Clara Huang
David Nkosi
Eva Johansson
Pro Tip:
COALESCE is the standard SQL way to handle NULLs. MySQL also has IFNULL(value, fallback) which does the same job but only takes two arguments. COALESCE is more portable — use it when you care about your SQL running across different database engines.
Production Insight
COALESCE is evaluated lazily — it returns the first non-NULL from left to right and stops evaluating further arguments.
COALESCE(a, expensive_function()) is safe — the function only runs if a is NULL.
For default display values in reports, COALESCE(col, 'N/A') is cleaner than CASE WHEN col IS NULL THEN 'N/A' ELSE col END.
Key Takeaway
COALESCE is the standard NULL-to-default function — use it in any column that might be NULL in reports.
COALESCE(a, b, c) returns the first non-NULL — multiple fallback values are supported.
Always COALESCE numeric columns before arithmetic when NULL should mean zero.
NULLIF and Aggregate Functions — NULLs You Create on Purpose
COALESCE turns NULLs into real values. NULLIF does the opposite — it turns a specific real value into NULL. Why on earth would you want that?
The classic example is preventing division by zero. If you divide by zero in SQL, you get an error. But if you use NULLIF(denominator, 0), SQL turns any zero denominator into NULL — and dividing by NULL returns NULL instead of crashing. You can then wrap that in COALESCE to display something readable.
Aggregate functions like COUNT, SUM, AVG, MIN, and MAX have a specific and important relationship with NULL: they all silently skip NULL values. This is usually what you want — averaging a salary column where some salaries aren't set yet should average the known salaries, not treat unknowns as zero. But it means COUNT(*) and COUNT(column_name) give different answers, which surprises many beginners.
COUNT(*) counts every row. COUNT(phone_number) counts only rows where phone_number is not NULL. This distinction matters enormously in reports.
nullif_and_aggregates.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
-- NULLIF(expression, unwanted_value)-- Returns NULL if expression equals unwanted_value, otherwise returns expression-- Use case: safe division — avoid divide-by-zero error-- Imagine a sales table with targets and actual salesCREATETABLEsales_performance (
rep_name VARCHAR(100),
target INT,
actual_sold INT
);
INSERTINTO sales_performance VALUES ('Nia Patel', 100, 85);
INSERTINTO sales_performance VALUES ('OmarSilva', 0, 0); -- new rep, no target setINSERTINTO sales_performance VALUES ('Priya Chen', 200, 210);
-- ❌ WRONG: This crashes when target = 0-- SELECT actual_sold / target FROM sales_performance;-- ✅ CORRECT: NULLIF converts 0 target to NULL, avoiding the crash-- COALESCE then turns the NULL result into a readable messageSELECT
rep_name,
target,
actual_sold,
COALESCE(
CAST(actual_sold ASDECIMAL) / NULLIF(target, 0) * 100,
0
) AS achievement_pct -- 0 when target was NULL/zeroFROM sales_performance;
-- ─────────────────────────────────────────-- Aggregate functions and NULL — key demo-- ─────────────────────────────────────────-- COUNT(*) counts ALL rows including those with NULLsSELECTCOUNT(*) AS total_employees
FROM employees;
-- Returns: 5-- COUNT(column) counts only NON-NULL values in that columnSELECTCOUNT(annual_bonus) AS employees_with_bonus
FROM employees;
-- Returns: 3 (Clara and David have NULL bonus — they are skipped)-- AVG also skips NULLs — it averages only the 3 known bonusesSELECTAVG(annual_bonus) AS average_known_bonus
FROM employees;
-- Returns: 3500.00 (4500 + 3200 + 2800) / 3-- If you want NULL bonuses treated as 0 in the average:SELECTAVG(COALESCE(annual_bonus, 0)) AS average_bonus_including_none
FROM employees;
-- Returns: 2100.00 (4500 + 3200 + 0 + 0 + 2800) / 5
Interviewers love asking 'what's the difference between COUNT() and COUNT(column_name)?' The answer is that COUNT() includes NULL rows in its count; COUNT(column_name) does not. Knowing this cold will impress any SQL interviewer.
Production Insight
NULLIF is the least-used NULL function but solves division-by-zero cleanly: revenue / NULLIF(clicks, 0) returns NULL instead of dividing by zero.
DIVIDE BY NULL produces NULL (not an error) in SQL — the NULLIF pattern exploits this to avoid exceptions.
NULLIF is also used to turn sentinel values like 0, -1, or 'N/A' into NULL for proper NULL handling downstream.
Key Takeaway
NULLIF(a, b) = NULL when a equals b — use it to turn zero into NULL before division to avoid divide-by-zero.
NULLIF is the clean alternative to CASE WHEN divisor = 0 THEN NULL ELSE numerator / divisor END.
Aggregate functions skip NULLs — converting unwanted values to NULL with NULLIF makes aggregates exclude them.
The Nullability Tax — Why Your Indexes Lie to You
You've been burned by this before: a query that should fly runs a full table scan. You check the execution plan, and there it is — a NULL-friendly column wrecking your index selectivity. NULLs are not values, but SQL Server still stores them. In a B-tree index, every NULL goes into the same linked list. That means a seek becomes a scan of all NULL rows before you find anything useful.
This isn't a bug. It's a design decision that punishes lazy schema design. If you filter WHERE column IS NULL, the engine can't do a simple equality check. It has to chase pointers. The fix? Filtered indexes. CREATE INDEX idx_active_orders ON orders (status) WHERE status IS NOT NULL. That index only lives where the data exists. Your query suddenly becomes logarithmic instead of linear.
The WHY is simple: NULL breaks the binary assumption of value comparison. Indexes rely on ordered value sets. NULL has no order. Don't let your database pretend otherwise.
FilteredIndexFix.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// io.thecodeforge — database tutorial
CREATETABLEorders (
order_id INTIDENTITYPRIMARYKEY,
status VARCHAR(20) NULL,
total DECIMAL(10,2)
);
// Without filtered index — full scan on status
SELECT order_id FROM orders WHERE status = 'SHIPPED';
// Create filtered index for non-null statuses only
CREATEINDEX idx_orders_shipped
ONorders (status)
WHERE status ISNOTNULL;
// Now the seek works
SELECT order_id FROM orders WHERE status = 'SHIPPED';
Output
Execution plan before: Table Scan (100% cost)
Execution plan after: Index Seek (nonclustered) (1% cost)
Production Trap:
A filtered index won't help queries filtering on IS NULL. Those still need a full scan. Design your schema so critical columns are NOT NULL if you need fast null lookups.
Key Takeaway
Filtered indexes turn NULL-crippled columns into performant weapons. Use them or accept the full table scan.
Three-Valued Logic — The Boolean Trap That Eats Joins
Here's where juniors burn hours debugging: you write a LEFT JOIN with a WHERE clause, and rows vanish. The culprit is three-valued logic. SQL doesn't do true/false like your programming language. It has TRUE, FALSE, and UNKNOWN. Any comparison with NULL produces UNKNOWN. And WHERE clauses only return rows where the condition is TRUE. UNKNOWN is treated as false.
Example: SELECT * FROM customers c LEFT JOIN orders o ON c.id = o.customer_id WHERE o.total > 100. This kills all customers with no orders — because NULL > 100 is UNKNOWN, not FALSE. The join returns them, but the WHERE filter discards them. You just turned a LEFT JOIN into an INNER JOIN without knowing it.
The fix is explicit: move the condition into the JOIN clause itself. LEFT JOIN orders o ON c.id = o.customer_id AND o.total > 100. Or use COALESCE to provide a default. But understand the logic first. Every time you write a WHERE clause on a nullable column from an outer join, you're playing Russian roulette with your result set.
JoinLogicMeltdown.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// io.thecodeforge — database tutorial
CREATETABLEcustomers (
id INTPRIMARYKEY,
name VARCHAR(50)
);
CREATETABLEorders (
id INTPRIMARYKEY,
customer_id INTNULL,
total DECIMAL(10,2)
);
INSERTINTO customers VALUES (1, 'Alice'), (2, 'Bob');
INSERTINTO orders VALUES (100, 1, 250.00), (101, NULL, NULL);
// The broken query — Alice vanishes
SELECT c.name, o.total
FROM customers c
LEFTJOIN orders o ON c.id = o.customer_id
WHERE o.total > 100;
-- Output: (empty set if no orders match)
// The correct query
SELECT c.name, o.total
FROM customers c
LEFTJOIN orders o ON c.id = o.customer_id AND o.total > 100;
Output
Broken query output:
name | total
------+-------
(0 rows)
Correct query output:
name | total
-------+-------
Alice | 250.00
Bob | NULL
Senior Shortcut:
Use the WHERE clause only on columns from the primary table of a LEFT JOIN. If you must filter on the nullable side, move the condition into the ON clause or use a subquery with IS NOT NULL inside.
Key Takeaway
Three-valued logic turns LEFT JOINs into INNER JOINs silently. Filter the nullable table in the ON clause, not the WHERE clause.
A Real-World Scenario
Why bother with NULL handling? Because NULLs silently break business logic in production. Consider an employee bonus system: your query calculates annual bonus as base_salary * performance_multiplier. If performance_multiplier is NULL (manager didn't submit review), the result is NULL, not zero. Payroll runs, employees with missing reviews get zero bonus — without any error or warning. The real cost hits when accounting runs reconciliation: the total bonus payout doesn't match the query output by thousands of dollars. You need to catch these NULL-drift paths before they corrupt downstream reports.
BonusNullDrift.sqlSQL
1
2
3
4
5
6
7
8
9
// io.thecodeforge — database tutorial
-- NULL multiplies through: NULL * 50000 = NULL, not 0SELECT
employee_id,
base_salary * performance_multiplier AS bonus_nulls,
COALESCE(base_salary * performance_multiplier, 0) AS bonus_safe
FROM employee_performance
WHERE performance_multiplier ISNULL;
Output
+-------------+------------+------------+
| employee_id | bonus_nulls| bonus_safe |
+-------------+------------+------------+
| 2041 | NULL | 0 |
| 2097 | NULL | 0 |
+-------------+------------+------------+
Production Trap:
Arithmetic with NULL never raises an error — it silently returns NULL. Always wrap multiplication chains in COALESCE at the final expression, not inside the calculation.
Key Takeaway
One NULL in a calculation chain poisons the entire result — always test with IS NULL to find drift paths.
Wrapping Up
Three lessons to carry forward. First, NULL is not a value — it's a marker for the absence of a value, and SQL's three-valued logic (TRUE, FALSE, UNKNOWN) means your WHERE clauses can silently exclude rows you expect to include. Second, use COALESCE to replace NULLs with defaults at query output, but never inside JOIN conditions — that masks missing relationships and corrupts referential integrity. Third, NULLIF and NULL-handling in aggregates let you intentionally exclude data (e.g., divide-by-zero prevention). Every NULL in your schema imposes a cognitive tax: each query writer must remember where NULLs live and how they propagate. Normalize your data to avoid optional columns where possible, and document every nullable column's business meaning in your schema comments.
NullGuardPattern.sqlSQL
1
2
3
4
5
6
7
8
9
10
// io.thecodeforge — database tutorial
-- Final pattern: safe aggregation with NULL handlingSELECT
department_id,
COUNT(*) AS total_employees,
COUNT(salary) AS employees_with_salary,
AVG(COALESCE(bonus, 0)) AS avg_bonus_with_default
FROM employees
GROUPBY department_id;
COUNT(column) counts non-NULL values only; COUNT(*) counts rows. Use this distinction to detect data quality issues in your tables.
Key Takeaway
Treat NULL as a design smell — minimize nullable columns and wrap all aggregations in explicit NULL handling.
● Production incidentPOST-MORTEMseverity: high
Bonus Payments Not Sent to 340 Employees Due to NULL Arithmetic
Symptom
340 employees didn't receive their quarterly payment. Payroll ran successfully without errors. The employees' base salary existed; their bonus_amount was NULL (not yet approved for that quarter).
Assumption
The calculation base_salary + bonus_amount would treat NULL bonus as zero, paying just the base salary component.
Root cause
NULL + any number = NULL. The payment calculation produced NULL for employees without an approved bonus, and the downstream system treated NULL total_compensation as 'no payment required'.
Fix
Changed to base_salary + COALESCE(bonus_amount, 0). Added a pre-run data quality check: SELECT COUNT(*) FROM employees WHERE total_compensation IS NULL before any payment batch.
Key lesson
NULL arithmetic is NULL — any calculation involving NULL produces NULL, not zero
Always COALESCE numeric columns that can be NULL before performing arithmetic
Add data quality checks before any financial batch run: assert zero NULL values in critical payment columns
Production debug guideDiagnosing invisible rows, wrong aggregates, and arithmetic surprises3 entries
Symptom · 01
WHERE filter returns zero rows but data definitely exists
→
Fix
You are using = NULL. Run SELECT COUNT(*) FROM table WHERE col IS NULL to confirm NULL rows exist. Then use IS NULL for the filter. WHERE col = NULL always returns zero rows because NULL = NULL evaluates to UNKNOWN.
Symptom · 02
SUM or AVG is lower than expected
→
Fix
NULL values are excluded from SUM and AVG. Run SELECT COUNT(), COUNT(col), SUM(col) together — the difference between COUNT() and COUNT(col) is your NULL row count. Use COALESCE(col, 0) inside the aggregate if zero is the correct treatment.
Symptom · 03
Calculated column shows NULL for some rows unexpectedly
→
Fix
One of the input columns is NULL. NULL arithmetic propagates: price NULL = NULL, price + NULL = NULL. Find which column is NULL with: SELECT id, price, discount, price (1 - discount) FROM products WHERE price * (1 - discount) IS NULL.
Aspect
IS NULL / IS NOT NULL
COALESCE
NULLIF
Purpose
Test whether a value is absent
Replace NULL with a fallback value
Turn a specific value into NULL
Returns
TRUE or FALSE
First non-NULL value from the list
NULL or the original value
Use in WHERE clause
Yes — primary use case
Yes — to filter on substituted values
Rarely — mainly in SELECT or arithmetic
Number of arguments
1 (the column to test)
2 or more (value then fallbacks)
Exactly 2
Prevents errors
No — detection only
Prevents NULL propagation in math
Prevents divide-by-zero crashes
SQL standard
Yes — all major databases
Yes — all major databases
Yes — all major databases
MySQL-specific alternative
None needed
IFNULL(value, fallback)
No direct alternative
Key takeaways
1
NULL means 'value unknown or absent'
it is NOT zero, NOT an empty string, and NOT false. It represents the total absence of information.
2
You can NEVER test for NULL with = or !=. Use IS NULL and IS NOT NULL exclusively
using = NULL silently returns zero rows with no error.
3
COALESCE(column, fallback) is your go-to tool for replacing NULL with a safe default
use it in SELECT to display fallbacks, in arithmetic to prevent NULL propagation, and in WHERE to filter on computed defaults.
COUNT(*) is the only one that counts every row regardless of NULLs. This distinction changes your totals and averages in ways that can silently break reports.
Common mistakes to avoid
3 patterns
×
Using = NULL in a WHERE clause
Symptom
The query returns zero rows silently — even when NULL rows definitely exist in the column. No error is thrown, making this especially hard to debug.
Fix
Use IS NULL for NULL equality: WHERE phone IS NULL. Use IS NOT NULL to exclude NULLs. The = operator produces UNKNOWN (not TRUE) when either operand is NULL, and UNKNOWN rows are excluded from WHERE results.
×
NULL arithmetic returning NULL instead of the expected value
Symptom
Calculated columns (base_salary + bonus) return NULL for rows where one input is NULL — payments skipped, totals wrong, no error raised
Fix
COALESCE every potentially-NULL numeric column before arithmetic: base_salary + COALESCE(bonus, 0). Add a data quality check before any batch calculation: SELECT COUNT(*) FROM table WHERE critical_column IS NULL should return zero.
×
Assuming COUNT(*) and COUNT(column) return the same value
Symptom
A report shows an unexpectedly low count — the discrepancy equals the number of NULL rows in the counted column, which COUNT(column) silently excludes
Fix
COUNT() counts all rows; COUNT(column) counts only non-NULL rows. Run both together: SELECT COUNT() AS all_rows, COUNT(email) AS with_email FROM users to expose the difference. Use COUNT(*) for row counts and COUNT(column) only when you explicitly want to exclude NULLs.
INTERVIEW PREP · PRACTICE MODE
Interview Questions on This Topic
Q01JUNIOR
Why does WHERE column_name = NULL return no rows in SQL?
Q02JUNIOR
What does COALESCE do and when would you use it?
Q03JUNIOR
You have a query that calculates total compensation as base_salary + bon...
Q01 of 03JUNIOR
Why does WHERE column_name = NULL return no rows in SQL?
ANSWER
In SQL, NULL represents an unknown value. The comparison expression NULL = NULL evaluates to UNKNOWN (SQL uses three-valued logic: TRUE, FALSE, UNKNOWN) — not TRUE. The WHERE clause only includes rows where the condition evaluates to TRUE, so rows where the comparison evaluates to UNKNOWN are excluded. This is why = NULL never matches any row. The correct operators are IS NULL (returns TRUE when the value is NULL) and IS NOT NULL (returns TRUE when the value is not NULL). These are special operators specifically designed to test for the absence of a value, where = cannot be used.
Q02 of 03JUNIOR
What does COALESCE do and when would you use it?
ANSWER
COALESCE(expr1, expr2, ..., exprN) evaluates its arguments from left to right and returns the first non-NULL value. If all arguments are NULL, it returns NULL. Common uses: (1) Default display values in reports: COALESCE(shipping_address, billing_address, 'No address on file'). (2) NULL-safe arithmetic: COALESCE(bonus_amount, 0) before addition to treat NULL as zero. (3) NULL-safe string operations: COALESCE(first_name, '') || ' ' || COALESCE(last_name, '') for full name concatenation. COALESCE is SQL-standard and works across all databases, making it preferable to database-specific functions like ISNULL (SQL Server) or IFNULL (MySQL).
Q03 of 03JUNIOR
You have a query that calculates total compensation as base_salary + bonus. Some employees have NULL bonus. What is the result for those rows?
ANSWER
The result is NULL. In SQL, any arithmetic expression involving NULL produces NULL — NULL + any_number = NULL, NULL * any_number = NULL. This is NULL propagation, and it applies to all arithmetic operators. For the employees with NULL bonus, total_compensation is NULL. This is frequently a bug — if the intent is that NULL bonus means 'no bonus' (i.e., zero), the correct expression is base_salary + COALESCE(bonus, 0). If NULL means 'bonus not yet determined' and those employees should be excluded from payment calculations, the NULL result may be intentional and should be documented explicitly.
01
Why does WHERE column_name = NULL return no rows in SQL?
JUNIOR
02
What does COALESCE do and when would you use it?
JUNIOR
03
You have a query that calculates total compensation as base_salary + bonus. Some employees have NULL bonus. What is the result for those rows?
JUNIOR
FAQ · 3 QUESTIONS
Frequently Asked Questions
01
Is NULL the same as an empty string or zero in SQL?
No — NULL, empty string (''), and zero (0) are three completely different things in SQL. NULL means no value exists at all. An empty string is a value — it's just a string with no characters. Zero is a numeric value. A column can hold '' or 0 and those are not NULL. Use IS NULL to check for true absence of data.
Was this helpful?
02
Why does NULL = NULL return false (or UNKNOWN) in SQL?
Because NULL means 'unknown', and two unknowns are not necessarily the same unknown. SQL follows three-valued logic: TRUE, FALSE, and UNKNOWN. Comparing NULL to anything — including another NULL — produces UNKNOWN, not TRUE. This is by design in the SQL standard. To check if two columns are both NULL or both the same value, use IS NOT DISTINCT FROM in PostgreSQL, or a combination of IS NULL checks in other databases.
Was this helpful?
03
What happens when I use NULL in a JOIN condition?
NULLs in JOIN columns are silently dropped. Because NULL = NULL is UNKNOWN (not TRUE), any row where the join column contains NULL on either side will not match and won't appear in the result set. This is a common source of mysteriously missing rows in query results. If you need to match NULL-to-NULL in a join, use IS NOT DISTINCT FROM (PostgreSQL) or COALESCE the column to a sentinel value that can't appear in real data.