尽管我们编写的大多数 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 >3000GROUPBY 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 >3000GROUPBY department_name
HAVINGAVG(salary)>5000;技术支持
SELECT name, salary *0.1AS bonus
FROM employees
JOIN orders ON customers.customer_id = orders.customer_id
WHERE salary >3000GROUPBY name
HAVINGAVG(salary)>5000;技术支持
SELECTDISTINCT department_id
FROM employees
JOIN orders ON customers.customer_id = orders.customer_id
WHERE salary >3000GROUPBY department
HAVINGAVG(salary)>5000;技术支持
ORDER BY 子句
ORDER BY 子句对特定列或表达式的结果集进行排序。与 WHERE 子句不同,ORDER BY 子句可以使用在 SELECT 语句中定义的列别名。
SELECTDISTINCT department_id
FROM employees
JOIN orders ON customers.customer_id = orders.customer_id
WHERE salary >3000GROUPBY department
HAVINGAVG(salary)>5000ORDERBY bonus DESC;技术支持
SELECTDISTINCT department_id
FROM employees
JOIN orders ON customers.customer_id = orders.customer_id
WHERE salary >3000GROUPBY department
HAVINGAVG(salary)>5000ORDERBY bonus DESCLIMIT10OFFSET5;-- OFFSET 5 ROWS FETCH NEXT 10 ROWS ONLY --SQL SERVER / ORACLE技术支持
MySQL 和 PostgreSQL 支持 LIMIT 与 OFFSET 子句。对于 SQL Server 和 Oracle,您可以使用 OFFSET、ROWS FETCH 和 ROWS ONLY 来限制查询返回的行数。
在编写 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 的执行顺序。下一节我们将逐步详细说明。
在下面的查询中,我添加了注释以标注各部分的评估先后。
我还编了一个便于记忆的藏头词组:For Work Goals and Hurdles,Search DataCamp's Organized Learning Opportunities。
SQL 查询执行的阶段
尽管我们编写的大多数 SQL 查询以
SELECT语句开头,但逻辑执行顺序是从FROM子句开始。这里,我将通过一些查询来展示查询的执行顺序。不过请注意,这些查询是不完整的,无法直接编译。FROM 子句
SQL 查询从
FROM子句开始执行。这通常是第一阶段,因为数据库需要确定数据源/表。当涉及多个表时,SQL 查询还会评估JOIN条件,将指定为数据源的表进行合并。下面这个不完整的查询会首先通过
FROM子句选取customers表的数据,并通过JOIN子句选取orders表的数据。WHERE 子句
在
FROM和JOIN子句之后执行WHERE子句,根据特定条件筛选行。需要注意的是,由于WHERE在SELECT之前处理,因此在SELECT子句中定义的别名不能在WHERE中直接引用。下面的查询使用
WHERE子句筛选bonus大于 5000 的员工记录。GROUP BY 子句
在筛选行之后,SQL 执行
GROUP BY子句,按指定列对结果进行分组。此步骤通常与COUNT()、SUM()、AVG()等聚合函数配合使用,以对指定列进行计算。下面的查询先筛选出
salary大于3,000的员工,然后按department分组,并为每个分组计算平均薪资。HAVING 子句
HAVING子句与WHERE类似,但用于在GROUP BY之后对分组数据进行筛选。在下面的查询中,SQL 按department对employees分组,计算每组的平均薪资,然后过滤掉平均薪资小于或等于5,000的分组。SELECT 子句
在
SELECT子句中,SQL 决定在前述步骤执行完毕后要返回的列或表达式。您可以在SELECT子句中应用算术运算、别名以及聚合函数。下面的查询使用
SELECT子句从employees表中检索name以及按salary * 0.1计算得到的bonus。DISTINCT 子句
在查询中,
DISTINCT子句在 SELECT 子句之后评估。DISTINCT子句通过仅返回唯一行,能有效去除表中的重复记录。下面的查询返回去重后的每个department_id。ORDER BY 子句
ORDER BY子句对特定列或表达式的结果集进行排序。与WHERE子句不同,ORDER BY子句可以使用在SELECT语句中定义的列别名。下面的查询按降序排列
bonus列。请注意,bonus是在SELECT语句中作为表达式的别名定义的。LIMIT/OFFSET 子句
LIMIT和OFFSET子句通常在 SQL 查询的最后执行,用于限制返回的行数。LIMIT指定要返回的最大行数,OFFSET指定在开始返回行之前要跳过的行数。下面的查询检索员工姓名和薪资,按
salary降序排序,并在跳过前 5 行后,将输出限制为 10 条结果。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子句中创建的别名来筛选行:乍一看这很合理,但会报错。为什么?因为在 SQL 的执行顺序中,
WHERE子句在SELECT之前被评估。要修复它,您需要使用子查询或改用HAVING:若想专门了解
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 解决业务问题的能力,从而在众多专业人士中脱颖而出。