The Core Idea
Written Order Is Not Execution Order
A SQL query is always written starting with SELECT, but the database engine doesn't process the clauses in that written order at all. It processes them in a fixed logical execution order: FROM (and JOIN) first, then WHERE, then GROUP BY, then HAVING, then SELECT, then ORDER BY, then LIMIT. Understanding this real order โ not the order you type the words in โ explains a huge number of SQL rules that otherwise seem arbitrary.
The reason this matters practically: at each stage, only the data and columns established by the PREVIOUS stages are available to work with. A clause literally cannot reference something that hasn't been computed yet in the real execution sequence, regardless of where it appears in the written query.
๐ก Memory Trick
Think of a restaurant kitchen assembly line: FROM is gathering all the raw ingredients from the pantry (the tables). WHERE is throwing out ingredients that don't meet a quality standard, one item at a time. GROUP BY is sorting the remaining ingredients into labeled bins. HAVING is inspecting entire BINS and discarding any bin that doesn't meet a group-level standard. SELECT is finally plating the specific dishes you asked for. ORDER BY is arranging the plated dishes on the table in a specific sequence. LIMIT is deciding how many plates actually make it to the table.
The Real Execution Order
Stage by Stage
1
FROM / JOIN
The database first identifies and combines all the source tables specified, including performing any joins โ this establishes the full, raw working set of rows before anything else happens.
2
WHERE
Filters individual rows from that raw working set based on a condition, BEFORE any grouping happens. Because grouping hasn't occurred yet, WHERE cannot reference aggregate functions like COUNT() or SUM() โ those don't exist yet at this stage.
3
GROUP BY
Collapses the remaining filtered rows into groups based on shared column values, preparing for aggregate calculations (like COUNT, SUM, AVG) to be computed per group.
4
HAVING
Filters entire GROUPS (not individual rows) based on a condition, often involving an aggregate function โ this is the group-level equivalent of WHERE, and it exists specifically because WHERE runs too early to filter on aggregate results.
5
SELECT
Only now does the database compute the actual columns and expressions you asked to see, including any calculated expressions or aliases you defined.
6
ORDER BY / LIMIT
Finally, the result set is sorted according to ORDER BY, and then LIMIT trims it down to the requested number of rows. Because SELECT already ran, ORDER BY can reference column aliases defined in SELECT โ a small but common source of confusion.
Why This Explains Common SQL Rules
The Practical Payoff
This execution order explains two classic SQL 'gotchas' directly. First, why WHERE can't use an aggregate function like WHERE COUNT(*) > 5 โ WHERE executes before GROUP BY, so aggregates simply don't exist yet at that stage; you must use HAVING instead. Second, why WHERE generally can't reference a column alias defined in SELECT (like WHERE total_price > 100 when total_price is an alias created in SELECT) โ SELECT hasn't run yet when WHERE is being evaluated, so that alias doesn't exist yet either.
ORDER BY, by contrast, CAN reference a SELECT alias, precisely because ORDER BY runs after SELECT in the real execution order โ the alias already exists by the time ORDER BY is evaluated, even though both clauses are written on opposite ends of the query.
๐ฅ๏ธ Applied Scenario
You're writing a query to find departments with more than 10 employees, and your first attempt uses WHERE COUNT(*) > 10, which fails with a syntax/semantic error.
1
You recall that WHERE executes before GROUP BY in the real logical order โ meaning individual rows haven't even been grouped yet, so COUNT(*) (a per-group aggregate) has no meaning at that stage.
2
You rewrite the query to GROUP BY department first, then filter the resulting GROUPS using HAVING COUNT(*) > 10 instead โ HAVING runs after GROUP BY, exactly when group-level aggregates like COUNT actually exist.
3
You also add a WHERE clause to filter out inactive employees BEFORE grouping, since that's a row-level filter that correctly belongs in the earlier WHERE stage, not HAVING.
4
Conclusion: correctly splitting row-level filtering (WHERE, before grouping) from group-level filtering (HAVING, after grouping) directly reflects the real execution order, not the order the clauses happen to be typed in.
๐ Exam Application
Exam questions frequently present a broken or inefficient query and ask you to identify why WHERE with an aggregate function fails, expecting you to explain the FROM โ WHERE โ GROUP BY โ HAVING โ SELECT โ ORDER BY โ LIMIT execution order as the reason. You may also be asked to state which clauses can and cannot reference a column alias defined in SELECT, based on whether that clause executes before or after SELECT in the real order.
โ ๏ธ Most Common SQL Execution Order Mistakes
The most common mistake is assuming SQL executes top-to-bottom in the order it's written โ SELECT is written first but executes fifth, which trips up anyone reasoning about a query based on its visual layout rather than its real logical execution order. Another frequent error is using WHERE when HAVING is actually needed (or vice versa) โ WHERE filters individual rows before grouping and cannot use aggregates; HAVING filters entire groups after grouping and is the only place aggregate-based filtering belongs.
โ Quick Self-Test
Can you write out the full logical execution order of a SQL query from memory (FROM through LIMIT)? Can you explain, using that order, exactly why WHERE cannot filter on an aggregate function but HAVING can?
Next Lesson
Database Normalization
โ
โ All Databases Lessons