What is the logical processing order of a SQL SELECT statement?
SQL processes clauses in this order: FROM, WHERE, GROUP BY, HAVING, SELECT, ORDER BY, LIMIT. This matters because it explains why you cannot use a SELECT alias in a WHERE clause, but you can use it in ORDER BY.
How to think about it
The order you write SQL differs from the order the engine evaluates it. This single mental model resolves almost every “why doesn’t this work?” SQL question — including alias scoping, WHERE vs. HAVING, and the ORDER BY exception.
The logical processing order (LPO)
Why you cannot use a SELECT alias in WHERE
WHERE runs at step 2. The aliases you name in SELECT are not computed until step 5 — they literally do not exist yet.
-- WRONG: alias net_price does not exist when WHERE runs
SELECT price * 0.9 AS net_price
FROM products
WHERE net_price < 50;
-- CORRECT: repeat the expression in WHERE
SELECT price * 0.9 AS net_price
FROM products
WHERE price * 0.9 < 50;
Why you CAN use a SELECT alias in ORDER BY
ORDER BY runs at step 6, after SELECT at step 5 — so aliases are fully computed and available.
-- WORKS: ORDER BY runs after SELECT
SELECT price * 0.9 AS net_price
FROM products
ORDER BY net_price;
Why aggregates work in HAVING but not WHERE
WHERE (step 2) fires before GROUP BY (step 3), so there are no aggregated groups yet. HAVING (step 4) fires after grouping — COUNT(*), SUM(amount), etc. are fully computed by then.
Run this and experiment — try moving the HAVING filter into a WHERE to see the error:
The ORDER BY + LIMIT trap
Note: MySQL and BigQuery allow SELECT aliases in HAVING as a convenience extension. PostgreSQL and SQL Server do not — they follow strict LPO. If portability matters, always use the expression directly.