MySQL-08.聚合函数和SQL(DQL)的执行流程
C-08 聚合(分组)函数和SQL(DQL)执行流程
上一章,讲到了SQL单行函数。实际上SQL函数还有一类,叫做聚合函数,或者多行,分组函数,它是对一组数据进行汇总的函数,输入的是一组数据的集合,输出的是单个值。
1.聚合函数的介绍
- 什么是聚合函数
- 聚合函数作用于一组数据,并对一组数据返回一个值。
- 常用的聚合函数
- AVG()
- SUM()
- MAX()
- MIN()
- COUNT()
- 聚合函数语法
/*
SELECT [column,] group function(column),...
FROM table_name
[WHERE condition]
GROUP BY column,
[ORDER BY column];
*/
- 聚合函数不能嵌套调用。比如不能出现类似
AVG(SUM(列名))
形式的调用。
1.1 AVG和SUM函数
只能对数值型的数据,使用AVG和SUM函数
mysql> SELECT AVG(salary),SUM(salary),MAX(salary),MIN(salary)
-> FROM employees;
+-------------+-------------+-------------+-------------+
| AVG(salary) | SUM(salary) | MAX(salary) | MIN(salary) |
+-------------+-------------+-------------+-------------+
| 6461.682243 | 691400.00 | 24000.00 | 2100.00 |
+-------------+-------------+-------------+-------------+
1 row in set (0.00 sec)
1.2 MIN和MAX函数
可以对任意类型的数据使用MIN和MAX函数,但是常用于数值型和字符类型,以及日期时间类型。
mysql> SELECT MIN(hire_date),MAX(hire_date)
-> FROM employees;
+----------------+----------------+
| MIN(hire_date) | MAX(hire_date) |
+----------------+----------------+
| 1987-06-17 | 2000-04-21 |
+----------------+----------------+
1 row in set (0.00 sec)
1.3 COUNT函数
- COUNT(*)返回表中记录总数,适用于任意数据类型。
mysql> SELECT COUNT(*) FROM employees;# COUNT(1)的效果类似于COUNT(*),相当于把一行数据看作1去计数
+----------+
| COUNT(*) |
+----------+
| 107 |
+----------+
1 row in set (0.00 sec
- COUNT(expr)返回expr不为空的记录总数
mysql> SELECT COUNT(commission_pct) FROM employees;
+-----------------------+
| COUNT(commission_pct) |
+-----------------------+
| 35 |
+-----------------------+
1 row in set (0.00 sec)
- COUNT(*),COUNT(1),COUNT(列名)那个好呢?
其实,对于MyISAM存储引擎的表是没有区别,这种引擎内部有一个计数器在维护者表的行数。
Innodb引擎的表用COUNT(*),COUNT(1)直接读行数,复杂度是O(n),因为Innodb真的要去读一遍,但好于具体的COUNT(列名)。 - 能不能使用COUNT(列名)替换COUNT(*)
不要使用COUNT(列名)来替代COUNT(*),COUNT(*)是SQL92定义的标准统计行数的语法,和数据库无关,和NULL和非NULL无关。COUNT(*)会统计值为NULL的行,而COUNT(列名)只会统计列名不为NULL值的行。
2.GROUP BY
2.1 基本使用
可以使用GROUP BY子句将表中的数据分成若干组。
/*
SELECT [column,] group function(column),...
FROM table_name
[WHERE condition]
[GROUP BY group_by_expression,]
[ORDER BY column];
*/
注意:
- GROUP BY子句一定要在WHERE后面。
- 在SELECT列表中所有未包含在组函数中的列,都应该包含在GROUP BY子句中。而包含在GROUP BY子句中的列不必包含在SELECT列表中。
举例:
SELECT department_id,AVG(salary)
FROM employees
GROUP BY department_id;
SELECT AVG(salary)
FROM employees
GROUP BY department_id;
2.2 使用多列分组
SELECT department_id,job_id,SUM(salary)
FROM employees
GROUP BY department_id,job_id;
-- GROUP BY job_id,department_id;结果一样
-- 和排序子句不同,顺序不一样也不会对结果有影响
2.3 GROUP BY中使用WITH ROLLUP
使用WITH ROLLUP关键字之后,在所有查询出的分组记录之后增加一条记录,该记录计算查询出所有记录的总和,即统计记录数量。
但是这个总和,和使用的分组函数有关。
mysql> SELECT department_id,AVG(salary)
-> FROM employees
-> WHERE department_id > 80
-> GROUP BY department_id WITH ROLLUP;
+---------------+--------------+
| department_id | AVG(salary) |
+---------------+--------------+
| 90 | 19333.333333 |
| 100 | 8600.000000 |
| 110 | 10150.000000 |
| NULL | 11809.090909 |
+---------------+--------------+
4 rows in set (0.00 sec)
3.HAVING
3.1 基本使用
过滤分组,HAVING子句
- 1.行已经被分组
- 2.使用了聚合函数
- 3.满足了HAVING子句中条件的分组将被显示
- 4.HAVING不能单独使用,必须要跟GROUP BY一起使用
/*
SELECT [column,] group function(column),...
FROM table_name
[WHERE condition]
[GROUP BY group_by_expression,]
[HAVING group_condition,]
[ORDER BY column];
*/
mysql> SELECT department_id,MAX(salary)
-> FROM employees
-> GROUP BY department_id
-> HAVING MAX(salary) > 10000;
+---------------+-------------+
| department_id | MAX(salary) |
+---------------+-------------+
| 20 | 13000.00 |
| 30 | 11000.00 |
| 80 | 14000.00 |
| 90 | 24000.00 |
| 100 | 12000.00 |
| 110 | 12000.00 |
+---------------+-------------+
6 rows in set (0.00 sec)
- 不能再WHERE子句中使用聚合函数
mysql> SELECT department_id,MAX(salary)
-> FROM employees
-> WHERE MAX(salary) > 10000
-> GROUP BY department_id;
ERROR 1111 (HY000): Invalid use of group function
3.2 WHERE和HAVING的对比
区别1:
WHER可以直接使用表中的字段作为筛选条件,但不能使用分组中的计算函数作为筛选条件,HAVING必须要与GROUP BY配合使用,可以把分组计算的函数和分组字段作为筛选条件。
区别2:
如果需要通过连接从关联表中获取需要的数据,WHERE是先筛选后连接,而HAVING是先连接后筛选。原因,再SQL的执行过程中会解释。
开发中的选择
WHERE和HAVING也不是互相互斥的,我们可以在一个查询里面同时使用WHERE和HAVING。包含分组统计函数的条件用HAVING,普通条件用WHERE,分组条件用HAVING。这样,才能发挥WHERE的高效快速,又发挥HAVING可以使用包含分组统计函数的查询条件的优点。当数据量很大的时候,运行效率会有很大的区别。
4.SELECT的执行过程
4.1 查询的结构
/*
方式1
SELECT ...,...,...
FROM ...,...,...
WHERE 表的连接条件 AND 不包含分组函数的过滤条件
GROUP BY 分组列名
HAVING 包含分组函数的过滤条件
ORDER BY ... ASC/DESC
LIMIT ...,...;
方式2
SELECT ...,...,...
FROM ... (LEFT / RIGHT OUTER) | (INNER)JOIN ... ON 连接条件
JOIN ... ON 连接条件
WHERE 不包含分组函数的过滤条件
GROUP BY 分组列名
HAVING 包含分组函数的过滤条件
ORDER BY ... ASC/DESC
LIMIT ...,...;
*/
4.2 SELECT 执行顺序
/*
执行顺序,FROM 子句,从哪些表里查询 -> WHERE / ON 使用表的连接条件进行过滤数据 -> 使用WHERE中的非连接条件过滤数据 ->
GROUP BY 对数据进行分组 -> HAVING 对分组后的数据进行过滤 -> SELECT 行过滤出显示的列 -> DISTINCT 有的话去重过滤 ->
ORDER BY 对数据进行排序 -> LIMIT进行分页
*/
-- 这里解释,为什么将不包含分组函数的条件写在WHERE会比HAVING中好,是因为,WHERE先执行,先过滤不满足的数据,可能会让数据量变小
-- 后续的执行就处理的数据量小,效率自然就高了
/*
关键字顺序
SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... LIMIT ...;
*/
4.3 执行原理
SELECT 是先执行FROM这一子句,在这个阶段,如果是多张表联合查询,还会经历下面的步骤:
- 1.首先先通过CROSS JOIN 求笛卡尔积,相当于得到虚拟表vt(virtual table) 1-1;
- 2.通过ON进行筛选,在虚拟表上vt1-1的基础上进行筛选,得到虚拟表vt1-2;
- 3.添加外部行。如果使用的是左连接,右连接,或者全连接就会涉及到外部行,也就是在虚拟vt1-3的基础上,增加外部行,得到虚拟表vt1-3。
当然如果我们操作的是两张以上的表,就会重复上面1-3步骤,知道表多处理完毕,得到原始数据。
当拿到了查询数据表的原始数据,也就是最终的虚拟表vt1,就可以在此基础上进行WHERE阶段,对vt1表的结果进行筛选过滤,得到虚拟表vt2。
然后进行第三步和第四步,也就是GROUP BY和HAVING阶段。在这个阶段中,实际上是在虚拟表vt2的基础上进行分组和过滤,得到中甲你的虚拟表vt3和vt4。
当完成了条件筛选部分后,就可以筛选表中提取的字段,也就是进入到SELECT和DISTINCT阶段。
首先在SELECT阶段会提取到想要的字段,然后在DISTINCT阶段过滤掉重复的行,分别得到中间的虚拟表vt5-1和vt5-2。
当提取到想要展示的字段数据之后,就可以按照指定的字段进行排序,也就是ORDER BY阶段,得到虚拟表vt6。
最后在vt6的基础上,取出指定行的记录,也就是LIMIT阶段,得到最终的结果,对应的是虚拟表vt7。
当然SELECT语句,并不一定会写所有的关键字,未写的相应的字段就会省略。
同时因为SQL是一门结构化语言,所以在写SELECT语句时,要注意相应的关键字顺序,执行原理,就是执行的顺序。
只是为了记录自己的学习历程,且本人水平有限,不对之处,请指正。