SQL 查询的逻辑执行顺序是什么?
简化版
SQL 查询的典型逻辑处理顺序是 FROM/JOIN → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT/OFFSET。这解释了为什么 WHERE 通常不能直接使用同层 SELECT 定义的别名,而 ORDER BY 通常可以。
详细版
SQL 的书写顺序不等于逻辑处理顺序:
书写:SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT
逻辑:FROM/JOIN → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT/OFFSET
各阶段先确定数据源和连接结果,再过滤行、分组、过滤分组,最后计算输出列并排序、截取结果。数据库真正的物理执行计划可以被优化器重排,例如选择不同连接顺序、下推谓词或使用索引,因此逻辑顺序是理解查询语义的模型,不是逐步执行的物理流水线。
完整版教学
一、先区分书写顺序和逻辑顺序
SQL 是声明式语言:开发者描述“想得到什么”,优化器决定“怎么得到”。逻辑处理顺序决定语句含义,物理执行计划则决定访问路径、连接算法和成本。
以查询部门平均工资为例:
SELECT department_id, AVG(salary) AS avg_salary
FROM employee
WHERE status = 'active'
GROUP BY department_id
HAVING AVG(salary) >= 10000
ORDER BY avg_salary DESC
LIMIT 5;
逻辑上先从 employee 取数据,用 WHERE 留下在职员工,再按部门分组,用 HAVING 留下平均工资达标的组,然后生成 SELECT 结果、排序并取前五行。
二、为什么 WHERE 不能使用 SELECT 别名
WHERE 处理时,同一查询层的 SELECT 列表尚未计算,因此下面的写法在 MySQL 等数据库中通常无效:
SELECT salary * 12 AS annual_salary
FROM employee
WHERE annual_salary > 200000;
应重复表达式,或者增加一层派生表/CTE:
SELECT *
FROM (
SELECT salary * 12 AS annual_salary
FROM employee
) AS e
WHERE annual_salary > 200000;
ORDER BY 位于 SELECT 之后,所以通常可以使用输出别名。不同数据库对别名解析有扩展规则,但不能把厂商扩展当成通用 SQL 规律。
三、WHERE 和 HAVING 为什么处在不同阶段
WHERE 过滤分组前的原始行,不能直接筛选本层聚合结果;HAVING 在分组后执行,可以使用聚合函数。
把能够在分组前判断的条件放到 WHERE,可以减少后续参与分组的数据量。只有依赖 COUNT、SUM、AVG 等分组结果的条件才需要放在 HAVING。
四、窗口函数位于哪里
窗口函数在 WHERE、GROUP BY、HAVING 和普通聚合之后计算,通常只能直接出现在 SELECT 或 ORDER BY 中。要筛选 ROW_NUMBER() 的结果,需要再套一层查询:
SELECT *
FROM (
SELECT e.*, ROW_NUMBER() OVER (
PARTITION BY department_id ORDER BY salary DESC
) AS rn
FROM employee AS e
) AS ranked
WHERE rn <= 3;
五、逻辑顺序不等于执行计划
优化器可以在不改变结果的前提下改写查询,例如把过滤条件尽早下推、把子查询转换为半连接、改变表的连接顺序。查看 EXPLAIN 得到的是物理执行策略,不能用执行计划中的节点顺序去否定 SQL 的逻辑处理顺序。
六、常见误区与追问
| 阶段 | 已经可见的内容 | 常见限制 |
|---|---|---|
WHERE | FROM/JOIN 形成的行 | 看不到 SELECT 别名和窗口结果 |
GROUP BY | 过滤后的输入行 | 决定聚合粒度 |
HAVING | 分组和聚合结果 | 可使用聚合条件 |
ORDER BY | 输出列与别名 | 多数数据库允许引用别名 |
- 误区:SQL 一定按书写顺序从 SELECT 开始执行。 逻辑处理顺序通常从
FROM/JOIN建立输入关系开始,SELECT是靠后的投影阶段。 - 误区:WHERE 可以直接使用 SELECT 里的别名。
WHERE逻辑上早于SELECT,此时别名还没有产生;可把表达式放子查询、CTE 或改用可见阶段。 - 误区:逻辑执行顺序就是物理执行计划。 优化器可以重排连接、下推谓词、选择索引;逻辑顺序解释语义,执行计划解释实际访问方式。
- 追问:为什么 HAVING 能用聚合函数? 因为
HAVING发生在GROUP BY和聚合计算之后,它过滤的是分组结果。 - 追问:窗口函数为什么不能直接写在 WHERE? 窗口计算晚于
WHERE,要筛窗口结果通常需要包一层子查询或 CTE。 - 追问:ORDER BY 为什么有时能用 SELECT 别名?
ORDER BY逻辑上靠后,投影别名已经形成,因此多数数据库允许它引用输出列别名。
记忆钩子:逻辑顺序解释“SQL 为什么这样写才有语义”,执行计划解释“数据库实际怎么把它跑快”。
七、加强记忆
先找表并连接,再用 WHERE 过滤行,然后 GROUP BY 分组、HAVING 过滤组,之后 SELECT 计算输出,最后 DISTINCT 去重、ORDER BY 排序并 LIMIT 截取。逻辑顺序解释语义和别名作用域,EXPLAIN 展示的则是优化器选择的物理执行方案。