Learner can put SQL clauses in the correct written order and can explain why the logical execution order differs.
Written order vs logical execution order
Written order vs logical execution order
SQL clauses must appear in a fixed written order: SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT. The database engine, however, does not execute them in that order. The logical execution order is: FROM → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT.
This distinction matters practically. Because WHERE runs before SELECT, you cannot use a column alias defined in SELECT inside a WHERE clause — the alias does not exist yet when WHERE is evaluated. For example, if you write SELECT amount * 1.2 AS adjusted FROM orders WHERE adjusted > 60, most databases will return an error because WHERE has not yet seen the SELECT output. You must write WHERE amount * 1.2 > 60 instead. ORDER BY, by contrast, runs after SELECT, so it can reference a SELECT alias.
HAVING runs after GROUP BY and can filter on the result of aggregate functions (like SUM or COUNT). WHERE cannot use aggregate functions because it runs before any grouping happens.
Walked example: SELECT city, SUM(amount) AS total FROM orders WHERE amount > 20 GROUP BY city HAVING SUM(amount) > 80 ORDER BY total DESC LIMIT 1 — execution steps: (1) FROM orders loads the table; (2) WHERE amount > 20 discards the Paris row with amount 20; remaining amounts: London 50, Paris 40, London 70, Paris 30; (3) GROUP BY city groups them: London total 120, Paris total 70; (4) HAVING SUM(amount) > 80 keeps only London (120 > 80); (5) SELECT computes city and total 120; (6) ORDER BY total DESC sorts (only one row here); (7) LIMIT 1 returns that one row.
Lesson notes
Written order vs logical execution order
SQL clauses must appear in a fixed written order: SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT. The database engine, however, does not execute them in that order. The logical execution order is: FROM → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT.
This distinction matters practically. Because WHERE runs before SELECT, you cannot use a column alias defined in SELECT inside a WHERE clause — the alias does not exist yet when WHERE is evaluated. For example, if you write SELECT amount * 1.2 AS adjusted FROM orders WHERE adjusted > 60, most databases will return an error because WHERE has not yet seen the SELECT output. You must write WHERE amount * 1.2 > 60 instead. ORDER BY, by contrast, runs after SELECT, so it can reference a SELECT alias.
HAVING runs after GROUP BY and can filter on the result of aggregate functions (like SUM or COUNT). WHERE cannot use aggregate functions because it runs before any grouping happens.
Walked example: SELECT city, SUM(amount) AS total FROM orders WHERE amount > 20 GROUP BY city HAVING SUM(amount) > 80 ORDER BY total DESC LIMIT 1 — execution steps: (1) FROM orders loads the table; (2) WHERE amount > 20 discards the Paris row with amount 20; remaining amounts: London 50, Paris 40, London 70, Paris 30; (3) GROUP BY city groups them: London total 120, Paris total 70; (4) HAVING SUM(amount) > 80 keeps only London (120 > 80); (5) SELECT computes city and total 120; (6) ORDER BY total DESC sorts (only one row here); (7) LIMIT 1 returns that one row.