跳至主要内容

SQL 执行顺序:理解查询如何运行

了解 SQL 的执行顺序及其与书写顺序的差异。编写准确且优化的查询以提升性能,并避免查询设计中的常见错误。
· 5分钟 阅读

使用 AI 探索

在编写 SQL 查询时,我们通常遵循某种顺序(SQL 也常被认为是“由内而外”书写)。然而,SQL 引擎在编译查询时遵循特定的执行顺序,这与通常的书写顺序不同。正如您将看到的,理解 SQL 的执行顺序对于掌握查询优化、提升查询的准确性和性能,以及调试复杂问题都至关重要。

开始之前,建议您学习 DataCamp 的 Introduction to SQL 课程和 SQL Fundamentals 技能路径,以学习 SQL 基础并了解如何通过查询提取数据。SQL Basics Cheat Sheet 则可作为过滤和聚合数据的常用 SQL 函数参考指南。

什么是 SQL 执行顺序?

SQL 执行顺序是指查询中不同子句被评估的先后次序。值得了解的一点是,执行顺序通常与我们编写 SQL 查询的顺序不同。以最简单的例子来说,您可能认为在 SELECT * FROM database 中,SELECT 会先被评估,但实际上执行顺序是从 FROM 子句开始的。

下面是 SQL 的执行顺序。下一节我们将逐步详细说明。

  • FROM/JOIN:指定要从哪些表中检索数据。
  • WHERE:在分组之前,根据条件筛选行。
  • GROUP BY:对具有相同特征的行进行分组。
  • HAVING:在分组后根据条件筛选分组。
  • SELECT:指定要检索或计算的列。
  • DISTINCT:从结果集中去除重复行。
  • ORDER BY:按指定列对结果集排序。
  • LIMIT:指定要返回的最大行数。
  • OFFSET:指定在开始返回行之前要跳过多少行。

在下面的查询中,我添加了注释以标注各部分的评估先后。

-- #6+7   SELECT DISTINCT department_id                                 
-- #1     FROM employees                                                
-- #2     JOIN orders ON customers.customer_id = orders.customer_id     
-- #3     WHERE salary > 3000                                          
-- #4     GROUP BY department 
-- #5     HAVING AVG(salary) > 5000 
-- #8     ORDER BY department 
-- #9     LIMIT 10 OFFSET 5 
-- #10    OFFSET 5 ROWS FETCH NEXT 10 ROWS ONLY; 

我还编了一个便于记忆的藏头词组:For Work Goals and Hurdles,Search DataCamp's Organized Learning Opportunities。

SQL 查询执行的阶段

尽管我们编写的大多数 SQL 查询以 SELECT 语句开头,但逻辑执行顺序是从 FROM 子句开始。这里,我将通过一些查询来展示查询的执行顺序。不过请注意,这些查询是不完整的,无法直接编译。

FROM 子句

SQL 查询从 FROM 子句开始执行。这通常是第一阶段,因为数据库需要确定数据源/表。当涉及多个表时,SQL 查询还会评估 JOIN 条件,将指定为数据源的表进行合并。

下面这个不完整的查询会首先通过 FROM 子句选取 customers 表的数据,并通过 JOIN 子句选取 orders 表的数据。

FROM customers
JOIN orders ON customers.customer_id = orders.customer_id;

WHERE 子句

在 FROM 和 JOIN 子句之后执行 WHERE 子句,根据特定条件筛选行。需要注意的是,由于 WHERE 在 SELECT 之前处理,因此在 SELECT 子句中定义的别名不能在 WHERE 中直接引用。

下面的查询使用 WHERE 子句筛选 bonus 大于 5000 的员工记录。

FROM employees
JOIN departments ON employees.employee_id = departments.department_id
WHERE salary * 0.1 > 5000;

GROUP BY 子句

在筛选行之后,SQL 执行 GROUP BY 子句,按指定列对结果进行分组。此步骤通常与 COUNT()、SUM()、AVG() 等聚合函数配合使用,以对指定列进行计算。

下面的查询先筛选出 salary 大于 3,000 的员工,然后按 department 分组,并为每个分组计算平均薪资。

FROM employees e
JOIN departments d ON e.employee_id = d.department_id
WHERE e.salary > 3000
GROUP BY e.name, d.department_name;

HAVING 子句

HAVING 子句与 WHERE 类似,但用于在 GROUP BY 之后对分组数据进行筛选。在下面的查询中,SQL 按 department 对 employees 分组,计算每组的平均薪资,然后过滤掉平均薪资小于或等于 5,000 的分组。

FROM employees
JOIN departments ON employees.employee_id = departments.department_id
WHERE salary > 3000
GROUP BY department_name
HAVING AVG(salary) > 5000;

SELECT 子句

在 SELECT 子句中,SQL 决定在前述步骤执行完毕后要返回的列或表达式。您可以在 SELECT 子句中应用算术运算、别名以及聚合函数。

下面的查询使用 SELECT 子句从 employees 表中检索 name 以及按 salary * 0.1 计算得到的 bonus。

SELECT name, salary * 0.1 AS bonus
FROM employees
JOIN orders ON customers.customer_id = orders.customer_id
WHERE salary > 3000
GROUP BY name
HAVING AVG(salary) > 5000;

DISTINCT 子句

在查询中,DISTINCT 子句在 SELECT 子句之后评估。DISTINCT 子句通过仅返回唯一行,能有效去除表中的重复记录。下面的查询返回去重后的每个 department_id。

SELECT DISTINCT department_id
FROM employees
JOIN orders ON customers.customer_id = orders.customer_id
WHERE salary > 3000
GROUP BY department
HAVING AVG(salary) > 5000;

ORDER BY 子句

ORDER BY 子句对特定列或表达式的结果集进行排序。与 WHERE 子句不同,ORDER BY 子句可以使用在 SELECT 语句中定义的列别名。

下面的查询按降序排列 bonus 列。请注意,bonus 是在 SELECT 语句中作为表达式的别名定义的。

SELECT DISTINCT department_id
FROM employees
JOIN orders ON customers.customer_id = orders.customer_id
WHERE salary > 3000
GROUP BY department
HAVING AVG(salary) > 5000
ORDER BY bonus DESC;

LIMIT/OFFSET 子句

LIMIT 和 OFFSET 子句通常在 SQL 查询的最后执行,用于限制返回的行数。LIMIT 指定要返回的最大行数,OFFSET 指定在开始返回行之前要跳过的行数。

下面的查询检索员工姓名和薪资,按 salary 降序排序,并在跳过前 5 行后,将输出限制为 10 条结果。

SELECT DISTINCT department_id
FROM employees
JOIN orders ON customers.customer_id = orders.customer_id
WHERE salary > 3000
GROUP BY department
HAVING AVG(salary) > 5000
ORDER BY bonus DESC
LIMIT 10 OFFSET 5;
-- OFFSET 5 ROWS FETCH NEXT 10 ROWS ONLY --SQL SERVER / ORACLE

MySQL 和 PostgreSQL 支持 LIMIT 与 OFFSET 子句。对于 SQL Server 和 Oracle,您可以使用 OFFSET、ROWS FETCH 和 ROWS ONLY 来限制查询返回的行数。

查看我们的教程 How to Use SQL OFFSET,了解更多关于数据分页以及各数据库对 OFFSET 与 LIMIT 子句的支持。

SQL 执行顺序与书写顺序

SQL 是一种声明式语言,这意味着查询的执行顺序与书写顺序不同。因此,您并非指定如何完成任务,而是声明您想要什么,由数据库引擎决定最佳的实现方式。这与 Python 或 Java 等命令式编程语言不同,后者需要您明确编写逐步的执行指令。

理解 SQL 的执行顺序会改变您构建查询的思路。例如,假设您编写了一个查询,想基于在 SELECT 子句中创建的别名来筛选行:

SELECT price * 0.9 AS discounted_price
FROM products
WHERE discounted_price > 100;

乍一看这很合理,但会报错。为什么?因为在 SQL 的执行顺序中,WHERE 子句在 SELECT 之前被评估。要修复它,您需要使用子查询或改用 HAVING:

SELECT price * 0.9 AS discounted_price
FROM products
HAVING discounted_price > 100;

若想专门了解 WHERE 与 HAVING,请阅读我们的教程:The Difference Between WHERE and HAVING in SQL。

常见错误与最佳实践

虽然查询的执行顺序不受书写顺序影响,但理解执行流程对于避免常见错误并提升查询性能至关重要。以下错误与对 SQL 执行顺序的误解直接相关:

常见错误

以下是可能阻碍查询性能的常见错误。

  • 在 WHERE 子句中使用列别名:由于 WHERE 先于 SELECT 执行,尝试在 WHERE 中使用别名会导致报错。理解 SQL 在 SELECT 之前评估 WHERE 可以让您知道,需要重复完整表达式而不是依赖别名。

  • 用 HAVING 来筛选行而不是 WHERE:HAVING 在 GROUP BY 之后执行,旨在筛选聚合数据。如果您筛选的是非聚合数据,应放在 WHERE 子句中。了解 WHERE 与 HAVING 在执行顺序上的差异 有助于确定条件应放置的位置。

  • 在未使用 GROUP BY 的 SELECT 中错误使用聚合:由于 GROUP BY 在 HAVING 或 SELECT 之前执行,在应用聚合函数前未对数据进行分组会导致结果不正确或报错。理解执行顺序能说明这两个子句为何必须配合使用。

  • 在 ORDER BY 子句中未正确使用别名:与 WHERE 不同,ORDER BY 在 SELECT 之后评估。这允许您使用在 SELECT 中创建的别名进行排序;了解别名何时可用有助于避免混淆。

最佳实践

请考虑以下最佳实践,确保您的查询按预期执行。

  • 使用 WHERE 尽早过滤:由于 WHERE 在 GROUP BY 和 JOIN 之前执行,及早应用过滤可以减少后续子句需要处理的行数,从而提升查询性能。通过尽可能早地过滤非聚合数据,您能减少需要分组或连接的数据量,节省处理时间。

  • 在连接前预先聚合数据:鉴于 FROM 与 JOIN 最先执行,使用子查询或公用表表达式(CTE)预先聚合数据,可以在连接前缩小数据集,确保在连接过程中需要处理的行更少。

  • 用索引优化 ORDER BY:由于 ORDER BY 是最后执行的步骤之一,确保参与排序的列已建立索引,有助于数据库更高效地处理排序操作,从而加快查询性能。

  • 在生产查询中避免 SELECT *:SELECT 在过滤、分组和聚合之后执行,只指定所需的列可以最小化检索的数据量,减少不必要的开销。

结论

理解 SQL 的执行顺序对于编写高效、准确、优化的查询非常重要。我们讨论了 SQL 中查询的逻辑执行顺序,并将其与书写顺序进行了比较。建议您多练习编写不同的查询,以更好地理解逻辑执行顺序。掌握这一概念将极大提升您在排查和优化 SQL 查询方面的能力。

如果您希望进一步提升 SQL 技能,建议尝试 DataCamp 的 Associate Data Analyst in SQL 职业路径,成为一名熟练的数据分析师。Reporting in SQL 课程也将帮助您熟练构建复杂报表和仪表盘,以更高效地展示数据。最后,您可以获得 SQL Associate Certification,展示您运用 SQL 解决业务问题的能力,从而在众多专业人士中脱颖而出。

Allan Ouko

作者

Allan Ouko

数据科学技术写作者,具备数据分析、商业智能和数据科学的一线实践经验。我撰写以行业为导向的实用内容,涵盖 SQL、Python、Power BI、Databricks 和数据工程,并以真实的分析工作为基础。我的写作兼顾技术深度与业务影响,帮助专业人士将数据转化为自信的决策。

SQL 执行顺序常见问答

SQL 的执行顺序与书写顺序有何不同?

SQL 的执行顺序通常从 FROM 子句开始,随后是 WHERE、GROUP BY 等子句,而书写顺序通常从 SELECT 语句开始。

JOIN 在执行顺序中处于哪个位置?

JOIN 操作作为 FROM 子句的一部分执行。

可以在 WHERE 子句中使用列别名吗?

不可以,列别名在 SELECT 子句中定义,而该子句在 WHERE 之后才执行。

WHERE 与 HAVING 有何区别?

WHERE 在分组前筛选行,而 HAVING 在 GROUP BY 之后筛选,并作用于聚合数据。

SQL 的执行顺序会影响查询性能吗?

是的,理解执行顺序可以通过及早应用过滤并减少不必要的操作来优化查询。

主题