Logical Execution Order of a SELECT Query
Although we write a query starting with SELECT, SQL Server logically processes it in a different order.
SELECT Department, COUNT(*) AS EmployeeCount
FROM Employees
WHERE Salary > 30000
GROUP BY Department
HAVING COUNT(*) >= 2
ORDER BY EmployeeCount DESC;
Logical execution order
| Order | Clause | Purpose |
|---|---|---|
| 1 | FROM / JOIN |
Identifies tables and combines data |
| 2 | ON |
Applies join conditions |
| 3 | WHERE |
Filters individual rows |
| 4 | GROUP BY |
Groups the remaining rows |
| 5 | HAVING |
Filters grouped results |
| 6 | SELECT |
Selects columns and calculates expressions |
| 7 | DISTINCT |
Removes duplicate results |
| 8 | ORDER BY |
Sorts the results |
| 9 | OFFSET / FETCH or TOP |
Limits the returned rows |
Simple flow
FROM / JOIN
↓
ON
↓
WHERE
↓
GROUP BY
↓
HAVING
↓
SELECT
↓
DISTINCT
↓
ORDER BY
↓
TOP / OFFSET / FETCH
Why is this important?
It explains why a SELECT alias normally cannot be used in WHERE:
SELECT Salary * 12 AS AnnualSalary
FROM Employees
WHERE AnnualSalary > 600000; -- Invalid
WHERE runs before SELECT, so AnnualSalary does not exist at that stage.
Correct version:
SELECT Salary * 12 AS AnnualSalary
FROM Employees
WHERE Salary * 12 > 600000;
However, an alias can be used in ORDER BY because it executes after SELECT:
SELECT Salary * 12 AS AnnualSalary
FROM Employees
ORDER BY AnnualSalary DESC;
Easy memory order
FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY
This shorter sequence is usually sufficient for interviews.