SQL Logical Execution Order & The NULL Trap
Master the exact 10-step logical execution sequence of SQL queries, 3-valued boolean logic, the NOT IN NULL hazard, and safe conditional expressions.
🔄 Logical Query Execution Order
While developers write SQL statements starting with the SELECT clause, the SQL database engine evaluates clause logic in a strictly defined 10-step execution sequence:
- Analogy: An Automated Factory Assembly Conveyor Belt.
- 1. FROM: Raw materials feed onto the conveyor belt from input bins (Table feeds & cross joins).
- 2. ON: Filtering individual component pairs before welding them together.
- 3. JOIN: Welding assembly parts together into a combined raw product chassis.
- 4. WHERE: Quality inspection scanners discarding defective product chassis before binning.
- 5. GROUP BY: Sorting passed items into distinct color-coded sorting bins.
- 6. HAVING: Weighing entire sorting bins and discarding bins that fall below target aggregate weight.
- 7. SELECT: Stamping brand decals and serial numbers onto items (evaluating math expressions & aliases).
- 8. DISTINCT: Shredding duplicate identical items passing down the track.
- 9. ORDER BY: Lining up finished boxes in numerical order for shipment.
- 10. LIMIT / OFFSET: Loading only the first crate of 10 boxes onto the delivery truck.
Written Syntax Order Engine Logical Execution Order
1. SELECT 1. FROM (Cross joins & table feeds)
2. FROM 2. ON (Filter join conditions)
3. WHERE 3. JOIN (Produce joined virtual table)
4. GROUP BY 4. WHERE (Filter individual rows)
5. HAVING 5. GROUP BY (Group rows into buckets)
6. ORDER BY 6. HAVING (Filter aggregated buckets)
7. LIMIT / OFFSET 7. SELECT (Evaluate math, columns & aliases)
8. DISTINCT (De-duplicate output rows)
9. ORDER BY (Sort final result set)
10. LIMIT / OFFSET (Slice page result)Critical Architectural Takeaways:
- The Alias Trap: Because
WHERE(step 4) is evaluated long beforeSELECT(step 7), you cannot reference column aliases declared inSELECTinside aWHEREclause. WHEREvsHAVING:WHEREfilters individual rows before grouping occurs.HAVINGfilters aggregated group buckets afterGROUP BYcompletes. Aggregate functions (SUM,AVG,COUNT) are invalid insideWHERE.
-- ❌ WRONG: Fails because 'annual_salary' alias and AVG() are evaluated prematurely in WHERE
SELECT dept_id, AVG(salary) AS avg_sal, salary * 12 AS annual_salary
FROM employees
WHERE annual_salary > 50000 AND AVG(salary) > 4000
GROUP BY dept_id;
-- ✅ CORRECT: Fixed query putting row filters in WHERE and group filters in HAVING
SELECT dept_id, AVG(salary) AS avg_sal
FROM employees
WHERE salary * 12 > 50000
GROUP BY dept_id
HAVING AVG(salary) > 4000;📦 The NULL Trap: 3-Valued Boolean Logic
In SQL, NULL does not equal 0, an empty string "", or FALSE. It represents an Unknown / Missing Value. SQL operates on 3-Valued Logic (TRUE, FALSE, UNKNOWN).
- Analogy: A Mystery Sealed Box. Comparing
box == 10returnsUNKNOWN. ComparingboxA == boxBreturnsUNKNOWNbecause you do not know the contents of either sealed box.
3-Valued Logic Operations
│
┌─────────────────────────────────┼─────────────────────────────────┐
▼ ▼ ▼
10 = NULL ──► UNKNOWN NULL = NULL ──► UNKNOWN TRUE AND UNKNOWN ──► UNKNOWNThree-Valued Logic Truth Tables
| Expression | Logical Result |
|---|---|
10 = NULL | UNKNOWN |
NULL = NULL | UNKNOWN (Use IS NULL instead!) |
NULL != NULL | UNKNOWN |
TRUE AND UNKNOWN | UNKNOWN |
FALSE AND UNKNOWN | FALSE |
TRUE OR UNKNOWN | TRUE |
FALSE OR UNKNOWN | UNKNOWN |
NOT UNKNOWN | UNKNOWN |
-- ❌ WRONG: Returns ZERO rows because comparison against NULL yields UNKNOWN
SELECT * FROM employees WHERE manager_id = NULL;
-- ✅ CORRECT: Uses explicit NULL comparison operators
SELECT * FROM employees WHERE manager_id IS NULL;[!CAUTION] The
NOT IN (..., NULL)Catastrophe: Evaluatingx NOT IN (1, 2, NULL)expands logically into:(x != 1 AND x != 2 AND x != NULL)Sincex != NULLresolves toUNKNOWN, any expression evaluated as(TRUE AND UNKNOWN)resolves toUNKNOWN. As a result, theWHEREclause filters out 100% of all rows, returning an empty result set!Best Practice: Always use
EXISTSor ensure subqueries used insideNOT INfilter outNULLvalues!
-- ❌ DANGEROUS: If department_id contains a single NULL row, this returns ZERO rows!
SELECT * FROM employees
WHERE dept_id NOT IN (SELECT dept_id FROM departments);
-- ✅ SAFE REWRITE 1: Filter NULLs in subquery
SELECT * FROM employees
WHERE dept_id NOT IN (SELECT dept_id FROM departments WHERE dept_id IS NOT NULL);
-- ✅ SAFE REWRITE 2: Use NOT EXISTS (Immune to the NULL trap!)
SELECT e.* FROM employees e
WHERE NOT EXISTS (
SELECT 1 FROM departments d WHERE d.dept_id = e.dept_id
);🛠️ Handling NULLs & Conditional Logic
COALESCE(val1, val2, ..., default): Returns the first non-null value in the argument list.NULLIF(expr1, expr2): ReturnsNULLifexpr1 = expr2. Crucial for preventing division-by-zero crashes.CASE WHEN ... THEN ... ELSE ... END: Evaluates conditional branching expressions.
-- Safe revenue per unit division avoiding division-by-zero crashes
SELECT product_id,
COALESCE(total_revenue / NULLIF(total_units, 0), 0) AS avg_unit_price,
CASE
WHEN total_units = 0 THEN 'Out of Stock'
WHEN total_units < 10 THEN 'Low Stock'
ELSE 'In Stock'
END AS inventory_status
FROM sales_summary;❓ Conceptual Quizzes
What is the result of executing: SELECT * FROM employees WHERE emp_id NOT IN (10, 20, NULL)?
Why does COALESCE(NULL, NULL, 'Default') return 'Default'?
💻 Practice Problems
Problem: Division-by-Zero Defensive Calculation
Write a query against a campaign_stats table (campaign_id, clicks, conversions) that calculates the conversion rate (conversions / clicks). If clicks is zero or NULL, the conversion rate must output 0.00 without crashing the database query.