本篇介绍SQL:2016(ISO/IEC 9075:2016)标准中定义的SQL查询语句中,各个子句之间的执行顺序,以及六种主流数据库中的实现及差异:Oracle、MySQL、Microsoft SQL Server、PostgreSQL、Db2、SQLite。
我们在前面的章节里介绍了基本的查询语句,以及各个子句的用法。在SQL标准中,除了SELECT
和FROM
子句之外,其他子句都是可选项。
接下来我们需要考虑一个问题:当查询中出现不同的子句时,它们的书写顺序;更重要的是,它们的执行顺序。我们将前文所介绍过的所有子句都写上:
SELECT DISTINCT t1.column1 AS alias1, t2.col2, aggregate_function(column3) FROM table1 t1 JOIN table2 t1 ON t1.column1 = t2.column1 WHERE conditions GROUP BY t1.column1, t2.col2 HAVING group_condition ORDER BY t1.column1 ASC, t2.col2 DESC OFFSET m ROWS FETCH NEXT num_rows ROWS ONLY;
以上就是SQL语法中常见的各个子句(当然不是全部子句)的书写顺序,但是它们的逻辑执行顺序却与此不同:
- 首先,
FROM
和JOIN
是SQL执行的第一步,它们从逻辑上决定了接下来要操作的数据集; - 其次,执行
WHERE
子句,对上一步的数据集进行过滤,保留满足条件的行;需要注意的是,此时还没有执行SELECT
子句,WHERE
条件中不能引用SELECT
列表中的列别名(alias1)或者聚合函数。不过,SQLite允许WHERE
条件中使用列别名(alias1); - 接下来,基于
GROUP BY
子句指定的表达式进行分组,分组条件有多少不同的取值,操作后的结果就有多少行; - 然后,基于分组结果执行聚合函数 aggregate_function,对于每个分组取值,生成一个函数结果;如果没有分组子句,基于所有结果执行一次聚合操作;
- 如果查询中使用了
GROUP BY
,可以使用HAVING
针对分组后的结果进一步进行过滤,通常是利用聚合函数的值进行过滤,如果是其他过滤条件,可以在WHERE
子句中提前指定,避免无谓的操作; - 接下来,
SELECT
子句可以选择要显示的列。如果存在GROUP BY
子句,SELECT
列表只能引用分组所使用的列,或者聚合函数;如果不存在GROUP BY
子句,可以引用FROM
和JOIN
指定的表中的任何列; - 如果在
SELECT
之后指定了DISTINCT
关键字,需要针对上一步的结果集进行去重操作,过滤掉所有重复的值; - 应用
ORDER BY
子句对上一步的结果进行最终的排序操作。如果存在GROUP BY
子句或者DISTINCT
关键字,只能使用SELECT
列表中出现的字段进行排序;如果不存在GROUP BY
子句和DISTINCT
,可以使用FROM
和JOIN
指定的表中的任何列; - 最后,
OFFSET
和FETCH
(或者LIMIT
、TOP
)限定了返回的行。
接下来,我们看一些示例。首先是外连接查询中的ON
条件和WHERE
条件的区别:
SELECT d.department_name, e.job_id, AVG(e.salary) AS avarage_salary FROM departments d LEFT JOIN employees e ON d.department_id = e.department_id WHERE d.department_name IN ('IT', 'Sales', 'Retail Sales') GROUP BY department_name, e.job_id ORDER BY d.department_name, e.job_id;
以上语句查询的是三个部门(IT、Sales、Retail Sales)的员工平均薪水,使用左外连接是因为 Retail Sales 部门还没有员工。查询结果如下:
我们修改一下上面的查询条件,只查询三个部门中职位为 IT_PROG 的员工平均薪水:
SELECT d.department_name, e.job_id, AVG(e.salary) AS avarage_salary FROM departments d LEFT JOIN employees e ON d.department_id = e.department_id AND e.job_id = 'IT_PROG' WHERE d.department_name IN ('IT', 'Sales', 'Retail Sales') GROUP BY department_name, e.job_id ORDER BY d.department_name, e.job_id;
先看查询结果:
结果中仍然显示了 3 条数据,但是 Sales 部门的平均薪水变成了 NULL。我们回头看一下查询中的职位过滤条件:
ON d.department_id = e.department_id AND e.job_id = 'IT_PROG'
因为JOIN ON
子句先执行,而且我们使用的是左外连接,左外连接一定会返回左表中的所有行,结果并不只显示职位为 IT_PROG 的员工平均薪水。如果想要达到这个效果,可以将AND e.job_id = 'IT_PROG'
放到WHERE子句中。
第二个例子我们看一下列别名的使用:
SELECT job_id AS job, AVG(salary) AS avarage_salary FROM employees WHERE job_id IN ('IT_PROG', 'ST_CLERK', 'SA_REP') GROUP BY job_id HAVING AVG(salary) > 5000 ORDER BY job_id;
以上语句查询不同职位的平均薪水,并且只返回大于 5000 的记录,在SELECT
、WHERE
、GROUP BY
以及ORDER BY
中都使用了 job_id 列,同时为该列指定了别名 job。考虑一个问题,在哪些子句中可以使用别名 job;同时,在HAVING子句中是否能够使用别名 avarage_salary。
按照 SQL 执行顺序,只有ORDER BY
在SELECT
之后执行,所以只能在ORDER BY
子句中使用列别名:
SELECT job_id AS job, AVG(salary) AS avarage_salary FROM employees WHERE job_id IN ('IT_PROG', 'ST_CLERK', 'SA_REP') GROUP BY job_id HAVING AVG(salary) > 5000 ORDER BY job; -- use column alias in ORDER BY clause
但是,并非所有的数据库都遵循该规则:
- MySQL、PostgreSQL、SQLite允许在
GROUP BY
中使用列别名 - MySQL、SQLite允许在
HAVING
中使用列别名 - SQLite允许在
WHERE
中使用列别名
-- For MySQL, PostgreSQL, SQLite
SELECT job_id AS job, AVG(salary) AS avarage_salary FROM employees WHERE job_id IN ('IT_PROG', 'ST_CLERK', 'SA_REP') GROUP BY job -- use column alias in GROUP BY clause HAVING AVG(salary) > 5000 ORDER BY job;
-- For MySQL, SQLite
SELECT job_id AS job, AVG(salary) AS avarage_salary FROM employees WHERE job_id IN ('IT_PROG', 'ST_CLERK', 'SA_REP') GROUP BY job HAVING avarage_salary > 5000 ORDER BY job;
-- For SQLite only
SELECT job_id AS job, AVG(salary) AS avarage_salary FROM employees WHERE job IN ('IT_PROG', 'ST_CLERK', 'SA_REP') -- use column alias in WHERE clause GROUP BY job HAVING avarage_salary > 5000 ORDER BY job;
第三个示例,使用了GROUP BY
对于HAVING
、SELECT
以及ORDER BY
的影响:
-- For SQLite only
SELECT job_id, first_name, AVG(salary) AS avarage_salary FROM employees WHERE job_id IN ('IT_PROG', 'ST_CLERK', 'SA_REP') GROUP BY job_id HAVING manager_id > 100 ORDER BY hire_date;
以上查询只有 SQLite 支持,需要注意三点:
-
SELECT
中引用了 first_name,但是该字段没有出现在GROUP BY
中。问题在于,我们按照职位进行分组,对于同一个职位可能存在多个员工,那么应该显示谁的名字呢?SQLite 针对每个分组随机选择一个 first_name,其他数据库会报错,MySQL比较复杂,它通过配置项(sql_mode)控制该行为,默认 only_full_group_by 也会报错(假如SELECT
中引用的是 job_title,而 job_title 可以由 job_id 决定,则不会报错),否则也是随机选择一个 first_name; -
HAVING
中引用了 manager_id,但是该字段没有出现在GROUP BY
中。HAVING
用于过滤分组后的数据,分组之后没有 manager_id,只有分组列和聚合结果。SQLite 是个例外,仍然允许HAVING
中使用这样的列; -
ORDER BY
中引用了 hire_date,但是该字段没有出现在GROUP BY
中。问题同样在于,对于同一个分组职位可能存在多个员工,那么应该按照谁的雇佣日期排序呢?SQLite 针对每个分组随机选择一个 hire_date 进行排序,其他数据库会报错,MySQL同样通过配置项(sql_mode)控制该行为。
看完留下点什么吧!欢迎评论!