Skip to content
wannacode

The order of query execution

In short: A SQL query is written starting with SELECT, but the database executes it in the order FROM, WHERE, GROUP BY, HAVING, SELECT, ORDER BY, LIMIT.

Problem

Works here, not there

In the lesson on groups, ORDER BY month used the alias from SELECT. In the lesson on HAVING we repeated the whole SUM(...), and SUM in WHERE gave an error. Why does an alias work in one place and not in another?

The answer is in the order in which the database reads the query. It is not the order in which you write it.

The idea

Written in one order, run in another

You write it like this:

SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT

The database thinks like this:

  1. FROM: where the rows come from;
  2. WHERE: which rows stay;
  3. GROUP BY: the groups;
  4. HAVING: which groups stay;
  5. SELECT: what is shown and what it is called;
  6. ORDER BY: the sorting;
  7. LIMIT: how many rows.

This is the logical order of execution of a query. The three busiest months, without the cancelled orders:

SQLdatabase: shop

Step by step: FROM takes the 60 orders. WHERE removes the 7 cancelled ones, and 53 are left. GROUP BY splits them into 14 months. HAVING keeps the six with at least 5 orders. SELECT works out the month and the count and names them. ORDER BY sorts them, and LIMIT takes the first three.

More precisely, if you are curious

This is the order in which the query must appear to run. The real plan can be different: the database may use an index or skip a step, as long as the result is the same. We will look at query plans in the module on indexes.

The idea

Why the alias works in ORDER BY

The alias is born in SELECT, at step 5. ORDER BY is at step 6, so the name already exists:

SQLdatabase: shop

The three biggest orders. This works in every database. In WHERE, GROUP BY and HAVING, by the rule, the alias does not exist yet, because they come before SELECT.

Try it

The most active customers

Show the five customers with the most orders: customer_id and the count in a column called orders, the most first. On a tie, the smaller customer_id comes first.

SQLdatabase: shop

See it

What SQLite forgives

By the rule, an alias in WHERE should not work. The products that cost under 1 € with a 20% discount:

SQLdatabase: shop

SQLite accepts it. It accepts an alias in GROUP BY too:

SQLdatabase: shop

Other databases are not as lenient. PostgreSQL accepts an alias in GROUP BY, but not in WHERE: there it says the column sale does not exist. SQL Server accepts it neither in WHERE nor in GROUP BY.

SQLite's leniency has limits. If the alias is the same as a column name, WHERE takes the column:

SQLdatabase: shop

Two products come out, not four: WHERE price < 1 looks at the real price, not the discounted one. And an alias of an aggregate in WHERE is still “misuse of aggregate”, because there are no groups yet at step 2:

SQLdatabase: shop

A query that works everywhere: in WHERE repeat the expression (WHERE price * 0.8 < 1), and use aliases freely in ORDER BY.

Exercise

The monthly report

The owner wants a report for 2026: for each month, how many orders there are (without the cancelled ones) and from how many different customers. Months with fewer than 3 orders are too quiet and she does not need them.

Show the columns month (year-month), orders and customers for the months of 2026 with at least 3 orders, not counting cancelled ones, in month order.

SQLdatabase: shop

Test

Check yourself

1. Which part does the database start with when it runs the query?
2. Why does ORDER BY total work when total is an alias from SELECT?
3. What is true about WHERE sale < 1, when sale is an alias from SELECT?
4. The query has ORDER BY orders DESC and LIMIT 3. When are the three rows taken?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·