CMD Guide
HomeDatabasesSQL Fundamentals

Order of SQL Query Execution

SQL is declarative: you describe the result you want, and the engine is free to compute it any way that produces that result — so there are really two orderings, and conflating them is the bug to avoid. The logical clause order is a semantic contract that defines what a query means (which names are in scope, what a row even is at each stage); the physical execution plan is what the optimizer actually runs, and it reorders, fuses, and skips steps freely as long as the answer matches the logical contract.

The logical clause order (the semantic contract)

This is the order a SQL engine pretends to evaluate clauses in. It is not about efficiency — it is about meaning. Each phase takes the relation produced by the previous phase and emits a new one, which is why a name created late cannot be used early.

  1. FROM / JOIN ... ON — take the source tables, form the row combinations, and apply each ON predicate. For an OUTER JOIN this is also where unmatched rows are padded with NULLs.
  2. WHERE — filter individual rows. No aggregates and no SELECT aliases are visible yet, because neither exists at this point.
  3. GROUP BY — collapse the surviving rows into one row per group. After this step a "row" is a group, and only grouping keys and aggregates are legal references.
  4. HAVING — filter groups using aggregate results (e.g. COUNT(*) > 3).
  5. SELECT — evaluate the output expressions and assign column aliases. This is the first moment an alias like AS total comes into existence.
  6. DISTINCT — deduplicate the projected rows.
  7. ORDER BY — sort. Because it runs after SELECT, it can reference output aliases.
  8. LIMIT / OFFSET (FETCH) — keep only the requested slice of the sorted output. Last, so a limit always applies to the fully filtered, grouped, and sorted result.

The two practical consequences fall straight out of this order: a SELECT alias is invisible to WHERE (alias is born in step 5, WHERE ran in step 2), and per-group conditions belong in HAVING, not WHERE.

diagram
diagram

Worked trace with real values

Two tables. We want, for each department with more than two engineers earning over $80k, the department and headcount — highest headcount first, top 2 only.

-- emp
id | name    | dept_id | salary
 1 | Asha    |   10    | 120000
 2 | Ben     |   10    |  95000
 3 | Cara    |   10    |  70000
 4 | Dev     |   20    |  88000
 5 | Esi     |   20    |  82000
 6 | Finn    |   30    |  99000

-- dept
dept_id | name
   10   | Platform
   20   | Payments
   30   | Growth

SELECT  d.name AS dept, COUNT(*) AS senior_count
FROM    emp e
JOIN    dept d ON e.dept_id = d.dept_id
WHERE   e.salary > 80000
GROUP BY d.dept_id, d.name
HAVING  COUNT(*) > 2
ORDER BY senior_count DESC
LIMIT 2;

Walking the logical contract phase by phase:

  1. FROM/JOIN ... ON → pair each emp with its dept by dept_id. All 6 emps match a dept, giving 6 joined rows.
  2. WHERE salary > 80000 → drop Cara (70k). 5 rows survive: Asha, Ben (dept 10); Dev, Esi (dept 20); Finn (dept 30).
  3. GROUP BY d.dept_id, d.name → three groups: 10 has 2 rows, 20 has 2 rows, 30 has 1 row.
  4. HAVING COUNT(*) > 2every group has 2 or fewer rows, so all three are dropped. Result is empty.
  5. SELECT, ORDER BY, LIMIT → nothing to project, sort, or slice. Final result: 0 rows.

The empty result is the point: it surprises people who mentally run HAVING COUNT(*) > 2 against the unfiltered headcount (dept 10 has 3 people total). But WHERE runs first and removed Cara, so the count HAVING sees is 2. Order changes the answer.

Now the physical side. Run the same query under EXPLAIN ANALYZE in Postgres and you do not see eight discrete steps. You see something like an index scan on emp(salary) that applies the WHERE while reading, a hash join, a HashAggregate that computes COUNT(*) and the HAVING filter together, and a bounded Top-N heapsort for ORDER BY ... LIMIT 2 that never materializes a full sort. Same answer, completely different sequence of operators.

Why the naive "processes data efficiently" framing is wrong

The original page said the database follows the clause order to "process data efficiently." That is backwards. The logical order is a correctness definition, and following it literally would often be the slow path. A real optimizer deliberately departs from it:

So the correct mental model is: logical order is the contract the engine must honor; the physical plan is whatever it judges cheapest while honoring that contract. When you need to know what actually ran, read EXPLAIN / EXPLAIN ANALYZE — do not infer it from clause order.

Pitfalls

Takeaways


Re-authored and deepened for this guide. The logical clause-evaluation order follows the SQL standard's conceptual processing model as described in the PostgreSQL documentation ("SELECT" reference and "Queries" chapter) and Itzik Ben-Gan's T-SQL Querying, which popularized the "logical query processing" phase model. The logical-vs-physical distinction, predicate pushdown, top-N sort, and join reordering draw on Hellerstein, Stonebraker & Hamilton's Architecture of a Database System and the PostgreSQL EXPLAIN / planner documentation. The original lesson's "processes data efficiently" framing was corrected because it conflated logical semantics with the physical execution plan; missing JOIN/ON, DISTINCT, and LIMIT/OFFSET phases were added.

🤖 Don't fully get this? Learn it with Claude

Stuck on Order of SQL Query Execution? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Order of SQL Query Execution** (Databases) and want to truly understand it. Explain Order of SQL Query Execution from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Order of SQL Query Execution** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Order of SQL Query Execution** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Order of SQL Query Execution** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes