Data Bear

SQL Order of Operations Explained Clearly

Power BI Sharing

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.Common Confusion: Aliases in WHERE Clauses

The Real SQL Execution Order (Simplified)

Here’s the actual order in which SQL statements are executed, not written:

  1. FROM – Identify the table(s) involved
  2. JOINs – Combine additional tables
  3. WHERE – Filter rows before any grouping
  4. GROUP BY – Organize rows for aggregation
  5. HAVING – Filter groups after aggregation
  6. SELECT – Choose the final columns
  7. DISTINCT – Remove duplicates from selected results
  8. ORDER BY – Sort the final result
  9. TOP / OFFSET – Limit how many rows are returned

Let’s explore each with examples.The Real SQL Execution Order (Simplified)

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?

  1. SQL locates the SalesOrderHeader table (FROM)
  2. Filters rows where TotalDue > 500 (WHERE)
  3. Selects the specific columns to return (SELECT)
  4. 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;Enter HAVING: Filtering After Aggregation
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.Why You Still Can’t Use Aliases in HAVING SQL order of operations

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.ORDER BY and TOP/OFFSET: Last in Line SQL order of operations

Quick Recap: Full SQL Execution Order

Here’s a condensed view of how SQL evaluates your query:

  1. FROM (including JOINs)
  2. WHERE (filter raw rows)
  3. GROUP BY (group rows)
  4. HAVING (filter groups)
  5. SELECT (return columns)
  6. DISTINCT (remove duplicates)
  7. ORDER BY (sort results)
  8. 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.