When you first start writing SQL, it feels pretty straightforward. You write a SELECT, point it at a table with FROM, maybe filter it with WHERE, and you’re off.
But once you start adding aggregations, grouping, and filtering on those aggregations, things can get confusing quickly. One of the most common issues people run into is not understanding the order in which SQL actually processes a query.
Even though we write SQL in a certain order, the database doesn’t execute it in that same sequence. Getting your head around this is key to avoiding errors and writing more efficient queries.
The Order We Write SQL
When writing a query, we always follow this structure:
SELECTFROMWHEREGROUP BYHAVINGORDER BY
This is the standard format you’ll see everywhere, and it’s how SQL is designed to be written.
The Order SQL Actually Runs
Here’s where things get interesting. The database does not execute your query in the same order you write it. Instead, it processes it like this:
FROMWHEREGROUP BYHAVINGSELECTORDER BY
This difference is exactly why certain things “don’t work” when you first try them.
Why This Is Important
1. You Can’t Use Aggregates in WHERE
A very common mistake is trying something like this:
SELECT customer_id, SUM(sales)
FROM orders
WHERE SUM(sales) > 1000
GROUP BY customer_id
This won’t work. WHERE runs before GROUP BY, and therefore before the SUM() is calculated. Instead, you need to use HAVING, which runs after aggregation:
SELECT customer_id, SUM(sales)
FROM orders
GROUP BY customer_id
HAVING SUM(sales) > 1000
2. WHERE Filters Rows, HAVING Filters Groups
Because of the execution order:
WHEREfilters individual rows before groupingHAVINGfilters aggregated results after grouping
Think of it like this:
WHEREdecides what data goes into the calculationHAVINGdecides which results come out of the calculation
3. Aliases Don’t Work Everywhere
Another confusing one is column aliases. You might write:
SELECT SUM(sales) AS total_sales
FROM orders
WHERE total_sales > 1000;
This fails because SELECT runs after WHERE, so total_sales doesn’t exist yet. But this works:
SELECT SUM(sales) AS total_sales
FROM orders
HAVING SUM(sales) > 1000;
A Simple Way to Remember It
If you’re ever stuck, just remember:
- SQL reads like:
SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY - But SQL runs like:
FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY
That mental model will save you a lot of debugging time.
Final Thoughts
Understanding the order of execution in SQL is one of those foundational concepts that unlocks everything else. Once it clicks, a lot of the “weird” errors you run into suddenly make sense. It also helps you write cleaner, more intentional queries, especially when working with aggregations and building more complex logic. Learning this, combined with the basic SQL commands I covered in my last blog, is key to learning SQL.
