从分组到派生表:SQL中的GROUP BY、HAVING与子查询深度解析

205 阅读3分钟

汇总、分组数据:Group ByHavingWhere

GROUP BY 和 HAVING 子句的用法

  • GROUP BY 用于将结果集按照指定的列进行分组。

  • HAVING 用于对分组后的结果进行筛选。

HAVINGWhere 区别

  • 执行顺序差异

在 SQL 查询中,各子句有特定的执行顺序,WHERE 子句在分组操作(GROUP BY)之前执行,而 HAVING 子句在分组操作之后执行。

  • WHERE 子句:在分组之前筛选出符合条件的原始行,减少参与分组的数据量,从而提高性能。

  • HAVING 子句:在分组之后对分组结果进行筛选,仅保留符合特定条件的分组。

  • 操作对象不同

  • WHERE 子句:只能对原始表中的列进行条件筛选,不能使用聚合函数。因为在 WHERE 子句执行时,分组和聚合操作尚未进行,聚合函数的值还未计算出来。

  • HAVING 子句:可以使用聚合函数,因为它是在分组和聚合操作完成后执行的,能够对分组后的结果进行筛选。

子查询

子查询的定义

子查询是嵌套在其他 SQL 语句中的查询,也就是我们先需要从表中,查询&筛选出一个虚拟数据集,再根据这个集合做一些操作。

子查询与 IN 操作符结合使用

  • 结合方式IN 操作符可和子查询搭配,用来判断某个值是否存在于子查询的结果集中。
  • 示例及处理流程:查询语句 
SELECT * FROM language WHERE language_id IN 
    (SELECT DISTINCT language_id FROM film);

中,MySQL 先执行子查询 SELECT DISTINCT language_id FROM film 得到结果集,然后依据此结果集执行外层查询,相当于执行 SELECT * FROM language WHERE language_id IN (具体结果值);

EXISTS 与子查询

  • EXISTS 的作用EXISTS 是布尔运算符,用于检验子查询是否会返回行。若子查询返回至少一行,EXISTS 就返回 TRUE;反之则返回 FALSE
  • 示例说明:如 
SELECT * FROM language WHERE EXISTS
    (SELECT * FROM film WHERE film.language_id = language.language_id);

子查询作为 EXISTS 子句的参数,用于查询符合条件的数据行。

派生表

  • 派生表的定义:当子查询处于 FROM 子句中时,该子查询就被叫做派生表。

  • 派生表的规则

    • 派生表一定要有别名,因为 MySQL 规定 FROM 子句中的表都得有名字。
  • 示例:在 

SELECT * FROM 
(SELECT last_name, COUNT(*) count FROM actor GROUP BY last_name) t 
WHERE t.last_name LIKE 'A%';

中,SELECT last_name, COUNT(*) count FROM actor GROUP BY last_name 就是一个派生表,它有别名 t

  • 与临时表区别:需注意,派生表并非临时表。

总结

  • 子查询是嵌套在其他 SQL 语句里的查询。
  • 子查询常作为比较运算的操作数,用在 WHERE 子句中。
  • 位于 FROM 子句中的子查询是派生表,派生表必须有别名

子查询

根据子查询是否依赖外部,分为两种子查询,独立子查询和依赖子查询。而依赖子查询会导致SQL的性能急剧下降。

依赖子查询

在mysql8.0以前,可能会被识别成依赖子查询,从而导致:

比如:

SELECT * FROM orders WHERE (o_clerk , o_orderdate) IN 
( SELECT o_clerk, MAX(o_orderdate) FROM orders GROUP BY o_clerk);

这种情况下,执行顺序是:

  1. 外部select * from orders 先取出一行(o_clerk, o_orderdate)
  2. 子查询根据当前行,执行一遍
  3. 重复1

这就导致了O(复杂度) = O(子查询行数 * 子查询行数)。

依赖子查询优化-子查询派生表

建立子查询派生表,通过外表和派生表的join操作,降低查询复杂度从: O(子查询行数 * 子查询行数) => O(常数*子查询行数)