UNDERSTANDING NULL TRAPS IN POSTGRESQL: AVOIDING COMMON PITFALLS
- kamalambalt
- Apr 16
- 6 min read
Introduction:
When working with SQL, handling NULL values is one of the trickiest parts of writing accurate queries. If you’re new to how NULL works in SQL, you may first want to read my earlier post on Handling NULLs in SQL:
A null trap occurs when a query unintentionally fails to account for NULLs, leading to missing data, incorrect results, or even confusing outputs. In this blog, we’ll explore what null traps are, why they happen, and how to avoid them.
WHAT IS A NULL TRAP?
“A NULL trap can be defined as unexpected or incorrect results caused by improper handling of NULL values.”
COMMON SCENARIOS WHERE WE MAY ENCOUNTER NULL TRAPS:
• Filtering
• Arithmetic
• Calculations and Aggregate Functions
• Subqueries ,Joins
• Group by, Order by
Filtering and Null Traps:
Comparisons with NULL return UNKNOWN, so rows are excluded
SELECT * FROM orders WHERE rental_date > '2026-01-01';If rental_date is NULL, the row is excluded. To include NULLs, we need:
SELECT * FROM orders
WHERE rental_date > '2026-01-01' OR rental_date IS NULL;Arithmetic and Null Traps:
Any arithmetic with Null will result in Null.
SELECT 10 + NULL;Result : NULL
---------------------------------------------------
Example:
Table: employees
employee_id salary bonus
1 50000 5000
2 60000 NULL
3 NULL 3000
4 55000 4000
SELECT employee_id, salary + bonus AS total_pay
FROM employees;Result:
employee_id total_pay
1 55000
2 NULL
3 NULL
4 59000
---------------------------------------------------------------------------
Safe version:
SELECT employee_id, COALESCE(salary,0) + COALESCE(bonus,0) AS total_pay FROM employees;Result:
employee_id total_pay
1 55000
2 60000
3 3000
4 59000
Aggregate Functions and Null Traps:
NULLs can be tricky when using aggregate functions. Usually, aggregate functions ignore nulls. “Expressions containing NULL values may result in rows being skipped unintentionally.”
Aggregate function on a single column:
SELECT SUM(salary) AS total_salary, SUM(bonus) AS total_bonus
FROM employees;Result:
total_salary total_bonus
165000 12000
✅ NULLs are ignored, only non-NULL values are summed.
--------------------------------------------------
Aggregate function on an expression:
SELECT SUM(salary + bonus) AS total_compensation
FROM employees;Step-by-step:
• Row-wise addition (salary + bonus):
employee_id salary + bonus
1 55000
2 NULL
3 NULL
4 59000
• SUM() adds only non-NULL results → 55000 + 59000 = 114000
Rows 2 and 3 are skipped because the expression evaluates to NULL—this is a classic NULL trap.
________________________________________
Safe way using COALESCE
SELECT SUM(COALESCE(salary,0) + COALESCE(bonus,0)) AS total_compensation FROM employees;• Replace NULL with 0 before addition
• Row-wise addition now:
employee_id salary + bonus (COALESCE)
1 55000
2 60000
3 3000
4 59000
• SUM() →total_compensation = 55000 + 60000 + 3000 + 59000 = 177000 ✅
________________________________________
✅ Key Takeaways:
1. Aggregate functions ignore NULLs by default.
2. Expressions with NULLs can produce NULL results, skipping rows in aggregates.
3. Use COALESCE() or NULLIF() to avoid null traps.
4. Always test queries with NULL values to ensure correct results.
NOT IN and Null Traps:
NOT IN is another common source of null traps in subqueries.
Consider an employees table where each employee may have a manager_id:
employee_id name manager_id
1 Alice NULL
2 Bob 1
3 Carol 1
4 Dave 2
5 Eve NULL
________________________________________
Goal
Find employees who are not managers (i.e., their employee_id is not used as a manager_id).
________________________________________
❌ Problematic Query Using NOT IN:
SELECT name FROM employees
WHERE employee_id NOT IN (SELECT manager_id FROM employees);What Happens?
The subquery returns:
( NULL, 1, 1, 2, NULL )
• Because manager_id contains NULL values, the list includes NULL.
• 👉 “Result: No rows returned ⚠️ (unexpected behavior)”
This is a classic null trap: the presence of NULL in the subquery causes the NOT IN condition to evaluate to UNKNOWN for all rows.”
________________________________________
✅ Fix 1: Filter Out NULLs
SELECT name FROM employees
WHERE employee_id NOT IN (SELECT manager_id FROM employees
WHERE manager_id IS NOT NULL);Result:
name
Carol
Dave
Eve
________________________________________
✅ Fix 2 (Recommended): Use NOT EXISTS
SELECT e.name FROM employees e
WHERE NOT EXISTS (SELECT 1 FROM employees m
WHERE m.manager_id = e.employee_id);Result:
name
Carol
Dave
Eve
NOT EXISTS safely ignores NULLs and is the preferred approach.
________________________________________
Key Insight
• NOT IN fails if the subquery returns even one NULL.
• Always:
o Filter out NULLs or
o Prefer NOT EXISTS
Joins and Null Traps:
Joins are used to combine data from multiple tables, but when NULL values are involved, they can lead to missing or misleading results. These are classic null traps in joins.
1. INNER JOIN and Missing Rows
Consider two tables:
employees
employee_id name department_id
1 Alice 10
2 Bob NULL
3 Carol 20
departments
department_id department_name
10 HR
20 IT
________________________________________
❌ INNER JOIN Example
SELECT e.name, d.department_name FROM employees e
JOIN departments d
ON e.department_id = d.department_id;Result:
name department_name
Alice HR
Carol IT
Bob is missing because department_id is NULL.
“INNER JOIN excludes rows where the join condition fails, and NULL values never match any value (NULL ≠ anything).”
________________________________________
✅ Fix: Use LEFT JOIN
SELECT e.name, d.department_name FROM employees e
LEFT JOIN departments d
ON e.department_id = d.department_id;Result:
name department_name
Alice HR
Bob NULL
Carol IT
LEFT JOIN preserves all rows from the left table, even when there is no match.
________________________________________
2. Filtering After LEFT JOIN (Hidden Null Trap)
A very common mistake:
SELECT e.name, d.department_name FROM employees e
LEFT JOIN departments d
ON e.department_id = d.department_id
WHERE d.department_name = 'HR';Result:
name department_name
Alice HR
Bob disappears again!
The WHERE clause removes NULLs, effectively turning the LEFT JOIN into an INNER JOIN.
________________________________________
✅ Correct Approach
Move the filter into the JOIN condition:
SELECT e.name, d.department_name FROM employees e
LEFT JOIN departments d
ON e.department_id = d.department_id
AND d.department_name = 'HR';Result:
name department_name
Alice HR
Bob NULL
Carol NULL
________________________________________
Key Takeaways
1. INNER JOIN drops rows when join columns contain NULLs.
2. LEFT JOIN preserves rows but may introduce NULLs in the result.
3. Filtering on joined columns in the WHERE clause can undo a LEFT JOIN.
4. Always be mindful that NULL = anything is not true in SQL.
5. Use LEFT JOIN and careful filtering to avoid losing data unintentionally.
Other Common Null Traps in SQL:
Apart from filtering, arithmetic, aggregates, subqueries, and joins, NULLs can also behave unexpectedly in GROUP BY and ORDER BY operations.
1. NULLs in GROUP BY
When grouping data, SQL treats NULL as a valid group value, not as “missing”.
Example:
SELECT department_id, COUNT(*) AS employee_count FROM employees
GROUP BY department_id;Behavior:
• All rows where department_id is NULL are grouped together.
• They form a separate group, just like any other department
Result:
department_id employee_count
10 5
20 3
NULL 2
Key Insight: NULL is not ignored in GROUP BY—it becomes its own group.
2. NULLs in ORDER BY
Sorting with NULLs can be misleading because their position is database-dependent.
Example:
SELECT name, bonus FROM employees
ORDER BY bonus;Behavior:
• In PostgreSQL, NULLs appear first by default.
• In others, they may appear last.
• This can lead to inconsistent results across systems.
________________________________________
Controlling NULL Position
In PostgreSQL, you can explicitly control where NULLs appear:
SELECT name, bonus FROM employees
ORDER BY bonus NULLS LAST; or
ORDER BY bonus NULLS FIRST;________________________________________
Key Takeaways
1. In GROUP BY, NULLs are treated as a valid grouping value, not ignored.
2. In ORDER BY, NULLs can appear first or last depending on the database.
3. Always explicitly handle NULL behavior when consistency matters.
4. Even “simple” clauses like sorting and grouping can hide subtle null traps.
________________________________________
💡 Final Thought:
Null traps aren’t limited to complex queries—they appear in every layer of SQL. Once we understand how NULL behaves in aggregates, subqueries, joins, grouping, and sorting, we can predict and avoid most unexpected query results.
Conclusion: Writing Safe SQL with NULL Awareness
NULLs are not errors—they are part of how SQL represents missing or unknown data. However, if not handled carefully, they can lead to incorrect results, missing rows, or misleading calculations.
Across different SQL operations, we saw how NULLs behave:
• In filters, they can exclude rows unexpectedly
• In arithmetic, they can propagate and turn results into NULL
• In aggregates, they are ignored unless explicitly handled
• In subqueries, they can break comparisons like NOT IN
• In joins, they can silently drop or misalign data
• In GROUP BY and ORDER BY, they can change grouping and sorting behavior
The key takeaway is simple:
SQL does not treat NULL as a value—it treats it as unknown.
To avoid null traps, always:
• Use COALESCE() to handle missing values explicitly
• Prefer NOT EXISTS over NOT IN when NULLs may exist
• Be careful with join conditions and filtering logic
• Test queries with real-world data, including NULL cases
Understanding NULL behavior is not just a technical detail—it’s essential for writing accurate, reliable, and production-safe SQL queries.

