Understanding SQL order of operations is essential for writing accurate and reliable queries. If you’ve ever been confused by alias errors in a WHERE clause or unexpected query results, you’re not alone. The issue often stems from assuming SQL runs your query top-to-bottom. In this post, we’ll break down how SQL actually executes your code step by step so you can avoid common pitfalls and build better queries.
In this guide, we’ll explore:
- Why SQL doesn’t execute top-to-bottom
- The actual sequence SQL engines follow
- When and why aliases don’t work in WHERE
- The real difference between WHERE vs. HAVING
- How GROUP BY, ORDER BY, and DISTINCT fit into execution
Whether you’re using T-SQL in SQL Server or working with MySQL, PostgreSQL, or another flavor, these rules apply across the board.
Common Confusion: Aliases in WHERE Clauses
Let’s say you write a query like this:
SELECT total_due AS total
FROM sales
WHERE total > 500;
You’ll get an error because SQL hasn’t yet “seen” your alias when it evaluates the WHERE clause.
That’s because the SELECT clause executes after the WHERE clause, and aliases defined in SELECT don’t yet exist during filtering. This one misunderstanding causes countless headaches.
The Real SQL Execution Order (Simplified)
Here’s the actual order in which SQL statements are executed, not written:
- FROM – Identify the table(s) involved
- JOINs – Combine additional tables
- WHERE – Filter rows before any grouping
- GROUP BY – Organize rows for aggregation
- HAVING – Filter groups after aggregation
- SELECT – Choose the final columns
- DISTINCT – Remove duplicates from selected results
- ORDER BY – Sort the final result
- TOP / OFFSET – Limit how many rows are returned
Let’s explore each with examples.
FROM → WHERE → SELECT → ORDER BY: A Step-by-Step Breakdown
Using a SalesOrderHeader table, consider this simple query:
SELECT SalesOrderID, TotalDue
FROM SalesOrderHeader
WHERE TotalDue > 500
ORDER BY TotalDue DESC;
What happens under the hood?
- SQL locates the
SalesOrderHeadertable (FROM) - Filters rows where
TotalDue > 500(WHERE) - Selects the specific columns to return (SELECT)
- Sorts the final data (ORDER BY)
Even though SELECT is written first, it executes much later.
GROUP BY, Aggregation & WHERE Clause Pitfalls
Now let’s say you want to group sales by customer and sum their purchases:
SELECT CustomerID, SUM(TotalDue) AS TotalSpent
FROM SalesOrderHeader
GROUP BY CustomerID
WHERE TotalSpent > 500; -- ❌ This will fail!
This throws an error because again, WHERE happens before aggregation, and SQL can’t filter on SUM(TotalDue) yet.
Enter HAVING: Filtering After Aggregation
The HAVING clause is the correct way to filter on aggregated values.
SELECT CustomerID, SUM(TotalDue) AS TotalSpent
FROM SalesOrderHeader
GROUP BY CustomerID
HAVING SUM(TotalDue) > 500;
Key Differences:
- WHERE filters individual rows before grouping
- HAVING filters grouped results after aggregation
This is why using SUM() in WHERE causes an error, but works perfectly in HAVING.
Why You Still Can’t Use Aliases in HAVING
Even though HAVING comes after GROUP BY, you still can’t use aliases like this:
HAVING TotalSpent > 500 -- ❌ Error!
That’s because SQL hasn’t yet “committed” the alias during execution. Always repeat the aggregation:
HAVING SUM(TotalDue) > 500
It may feel repetitive, but it’s the correct approach.
DISTINCT: When You Need Unique Rows
Say you want to get a list of unique customers who have made purchases:
SELECT DISTINCT CustomerID
FROM SalesOrderHeader;
DISTINCT is applied after SELECT it filters duplicates from the final list of returned columns.
ORDER BY and TOP/OFFSET: Last in Line
Because ORDER BY comes after SELECT, you can use column aliases here:
SELECT SUM(TotalDue) AS TotalSpent
FROM SalesOrderHeader
GROUP BY CustomerID
ORDER BY TotalSpent DESC;
This works because TotalSpent has been defined during SELECT, and ORDER BY happens afterward.
Similarly, using TOP or OFFSET to limit results will happen at the very end.
Quick Recap: Full SQL Execution Order
Here’s a condensed view of how SQL evaluates your query:
- FROM (including JOINs)
- WHERE (filter raw rows)
- GROUP BY (group rows)
- HAVING (filter groups)
- SELECT (return columns)
- DISTINCT (remove duplicates)
- ORDER BY (sort results)
- TOP / OFFSET (limit output)
Key Takeaways
Don’t reference aliases in WHERE or HAVING
Use WHERE for row filtering, HAVING for aggregated filtering
Understand the true execution flow, not the written order
GROUP BY comes before SELECT, so prepare your aggregations accordingly
ORDER BY and OFFSET always come last
Continue Learning SQL the Right Way
Understanding SQL order of operations will transform how you write and troubleshoot queries.
If you want a structured learning path, check out the T-SQL Introduction Course perfect for SQL beginners and intermediates wanting to solidify fundamentals.


