MySQL学习笔记(六)

悠悠 2023-09-29 12:47 45阅读 0赞

目录

一:聚合函数

二:GROUP BY

三: HAVING

四: SELECT的执行过程

五:子查询


一:聚合函数

1.1 什么是聚合函数:

聚合函数作用于一组数据,并对一组数据返回一个值。

4f785fee450d4657b0fc3baf5787b25a.png

1.2 聚合函数类型:

  • AVG()
  • SUM()
  • MAX()
  • MIN()
  • COUNT()

1.3 聚合函数语法:

27e5b306875a4e02af445dabb26dfdd1.png

聚合函数不能嵌套调用。比如不能出现类似“AVG(SUM(字段名称))”形式的调用。

1.4 AVG和SUM函数:

可以对数值型数据使用AVG 和 SUM 函数。

  1. SELECT AVG(salary), MAX(salary),MIN(salary), SUM(salary)
  2. FROM employees
  3. WHERE job_id LIKE '%REP%';

1.5 MIN和MAX函数:

可以对任意数据类型的数据使用 MIN 和 MAX 函数。

  1. SELECT MIN(hire_date), MAX(hire_date)
  2. FROM employees;

1.6 COUNT函数:

COUNT(*)返回表中记录总数,适用于任意数据类型。

  1. SELECT COUNT(*)
  2. FROM employees
  3. WHERE department_id = 50;

COUNT(expr) 返回expr不为空的记录总数。

  1. SELECT COUNT(commission_pct)
  2. FROM employees
  3. WHERE department_id = 50;

1.6.1 问题:用count(*),count(1),count(列名)谁好呢?
其实,对于MyISAM引擎的表是没有区别的。这种引擎内部有一计数器在维护着行数。
Innodb引擎的表用count(*),count(1)直接读行数,复杂度是O(n),因为innodb真的要去数一遍。但好于具体的count(列名)。
1.6.1 问题:能不能使用count(列名)替换count(*)?
不要使用 count(列名)来替代 count(*) , count(*) 是 SQL92 定义的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。
说明:count(*)会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行。

2cb6d3295ed64c7e833b45a18e8a98ad.png

二:GROUP BY

2.1 基本使用:

3b6d7bb2e1d1440c8abf6f6219215665.png

可以使用GROUP BY子句将表中的数据分成若干组:

  1. SELECT column, group_function(column)
  2. FROM table
  3. [WHERE condition]
  4. [GROUP BY group_by_expression]
  5. [ORDER BY column];

明确:WHERE一定放在FROM后面。

在SELECT列表中所有未包含在组函数中的列都应该包含在 GROUP BY子句中

  1. SELECT department_id, AVG(salary)
  2. FROM employees
  3. GROUP BY department_id ;

7a9581e0f5704ca88df273142136d5f6.png

包含在 GROUP BY 子句中的列不必包含在SELECT 列表中

  1. SELECT AVG(salary)
  2. FROM employees
  3. GROUP BY department_id ;

GROUP BY声明在FROM后面、WHERE后面,ORDER BY前面,Limit前面。

2.2 使用多个列分组:

  1. # 需求:查询各个department_id,job_id的平均工资
  2. #方式一:
  3. SELECT department_id,job_id,AVG(salary)
  4. FROM employees
  5. GROUP BY department_id,job_id;
  6. #方式二:
  7. SELECT job_id,department_id,AVG(salary)
  8. FROM employees
  9. GROUP BY job_id,department_id;

2.3 GROUP BY中使用WITH ROLLUP :

使用 WITH ROLLUP 关键字之后,在所有查询出的分组记录之后增加一条记录,该记录计算查询出的所有记录的总和,即统计记录数量。

  1. SELECT department_id,AVG(salary)
  2. FROM employees
  3. WHERE department_id > 80
  4. GROUP BY department_id WITH ROLLUP;

注意:
当使用ROLLUP时,不能同时使用ORDER BY子句进行结果排序,即ROLLUP和ORDER BY是互相排斥的。

三: HAVING

3.1 基本使用:

b6e965998a354be5bbb46d7b974d7f1e.png

过滤分组:HAVING子句

  1. 行已经被分组。
  2. 使用了聚合函数。
  3. 满足HAVING 子句中条件的分组将被显示。
  4. HAVING 不能单独使用,必须要跟 GROUP BY 一起使用。
  1. SELECT department_id, MAX(salary)
  2. FROM employees
  3. GROUP BY department_id
  4. HAVING MAX(salary)>10000 ;

结论一:如果过滤条件中使用了聚合函数,则必须使用HAVING来替换WHERE。否则就会报错。

  1. #查询部门id以及部门最高工资大于10000
  2. SELECT department_id,MAX(salary)
  3. FROM employees
  4. GROUP BY department_id
  5. HAVING MAX(salary)>10000;

结论二:HAVING必须声明在GROUP BY的后面。

3.2 WHERE和HAVING的对比:

区别1:WHERE 可以直接使用表中的字段作为筛选条件,但不能使用分组中的计算函数作为筛选条件;
HAVING 必须要与 GROUP BY 配合使用,可以把分组计算的函数和分组字段作为筛选条件。这决定了,在需要对数据进行分组统计的时候,HAVING 可以完成 WHERE 不能完成的任务。这是因为,在查询语法结构中,WHERE 在 GROUP BY 之前,所以无法对分组结果进行筛选。HAVING 在 GROUP BY 之后,可以使用分组字段和分组中的计算函数,对分组的结果集进行筛选,这个功能是 WHERE 无法完成的。另外,WHERE排除的记录不再包括在分组中。

区别2:如果需要通过连接从关联表中获取需要的数据,WHERE 是先筛选后连接,而 HAVING 是先连接后筛选。

这一点,就决定了在关联查询中,WHERE 比 HAVING 更高效。因为 WHERE 可以先筛选,用一个筛选后的较小数据集和关联表进行连接,这样占用的资源比较少,执行效率也比较高。HAVING 则需要先把结果集准备好,也就是用未被筛选的数据集进行关联,然后对这个大的数据集进行筛选,这样占用的资源就比较多,执行效率也较低。

9f09c36cda0b45328c58696489081399.png

开发中的选择:
WHERE 和 HAVING 也不是互相排斥的,我们可以在一个查询里面同时使用 WHERE 和 HAVING。包含分组统计函数的条件用 HAVING,普通条件用 WHERE。这样,我们就既利用了 WHERE 条件的高效快速,又发挥了 HAVING 可以使用包含分组统计函数的查询条件的优点。当数据量特别大的时候,运行效率会有很大的差别。

即:

1.当过滤条件中没有聚合函数时,则此过滤条件必须声明在HAVING中。

2.当过滤条件中没有聚合函数时,则此过滤条件声明在WHERE中或HAVING中都可以。但是,建议声明在WHERE中。

  1. #查询部门id为10,20,30,40这4个部门中最高工资比10000高的部门信息
  2. #方式一:
  3. SELECT department_id,MAX(salary)
  4. FROM employees
  5. WHERE department_id,IN(10,20,30,40)
  6. GROUP BY department_id
  7. HAVING MAX(salary)>10000;
  8. #方式二:
  9. SELECT department_id,MAX(salary)
  10. FROM employees
  11. GROUP BY department_id
  12. HAVING MAX(salary)>10000 AND department_id IN(10,20,30,40);
  13. #方式一的运行效率大于方式二

四: SELECT的执行过程

4.1 查询的结构:

  1. #方式1(sql92):
  2. SELECT ...,....,...
  3. FROM ...,...,....
  4. WHERE 多表的连接条件
  5. AND 不包含组函数的过滤条件
  6. GROUP BY ...,...
  7. HAVING 包含组函数的过滤条件
  8. ORDER BY ... ASC/DESC
  9. LIMIT ...,...
  10. #方式2(sql99):
  11. SELECT ...,....,...
  12. FROM ... JOIN ...
  13. ON 多表的连接条件
  14. JOIN ...
  15. ON ...
  16. WHERE 不包含组函数的过滤条件
  17. AND/OR 不包含组函数的过滤条件
  18. GROUP BY ...,...
  19. HAVING 包含组函数的过滤条件
  20. ORDER BY ... ASC/DESC
  21. LIMIT ...,...
  22. #其中:
  23. #(1)from:从哪些表中筛选
  24. #(2)on:关联多表查询时,去除笛卡尔积
  25. #(3)where:从表中筛选的条件
  26. #(4)group by:分组依据
  27. #(5)having:在统计结果中再次筛选
  28. #(6)order by:排序
  29. #(7)limit:分页

4.2 SELECT执行顺序:

4.2.1 关键字的顺序是不能颠倒的:

  1. SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... LIMIT...

4.2.2 SELECT 语句的执行顺序(在 MySQL 和 Oracle 中,SELECT 执行顺序基本相同):

  1. FROM -> WHERE -> GROUP BY -> HAVING -> SELECT 的字段 -> DISTINCT -> ORDER BY -> LIMIT

89d123138a8d4229b15eba219606fd6a.png

  1. SELECT DISTINCT player_id, player_name, count(*) as num # 顺序 5
  2. FROM player JOIN team ON player.team_id = team.team_id # 顺序 1
  3. WHERE height > 1.80 # 顺序 2
  4. GROUP BY player.team_id # 顺序 3
  5. HAVING num > 2 # 顺序 4
  6. ORDER BY num DESC # 顺序 6
  7. LIMIT 2 # 顺序 7

在 SELECT 语句执行这些步骤的时候,每个步骤都会产生一个 虚拟表 ,然后将这个虚拟表传入下一个步骤中作为输入。需要注意的是,这些步骤隐含在 SQL 的执行过程中,对于我们来说是不可见的。

4.3 SQL 的执行原理:

(一)SELECT 是先执行 FROM 这一步的。在这个阶段,如果是多张表联查,还会经历下面的几个步骤:

  1. 首先先通过 CROSS JOIN 求笛卡尔积,相当于得到虚拟表 vt(virtual table)1-1;
  2. 通过 ON 进行筛选,在虚拟表 vt1-1 的基础上进行筛选,得到虚拟表 vt1-2;
  3. 添加外部行。如果我们使用的是左连接、右链接或者全连接,就会涉及到外部行,也就是在虚拟
    表 vt1-2 的基础上增加外部行,得到虚拟表 vt1-3。

当然如果我们操作的是两张以上的表,还会重复上面的步骤,直到所有表都被处理完为止。这个过程得到是我们的原始数据。
(二)当我们拿到了查询数据表的原始数据,也就是最终的虚拟表 vt1 ,就可以在此基础上再进行 WHERE 阶段 。在这个阶段中,会根据 vt1 表的结果进行筛选过滤,得到虚拟表 vt2 。
(三)然后进入第三步和第四步,也就是 GROUP 和 HAVING 阶段 。在这个阶段中,实际上是在虚拟表 vt2 的基础上进行分组和分组过滤,得到中间的虚拟表 vt3 和 vt4 。
(四)当我们完成了条件筛选部分之后,就可以筛选表中提取的字段,也就是进入到 SELECT 和 DISTINCT阶段 。
(五)首先在 SELECT 阶段会提取想要的字段,然后在 DISTINCT 阶段过滤掉重复的行,分别得到中间的虚拟表vt5-1 和 vt5-2 。
(六)当我们提取了想要的字段数据之后,就可以按照指定的字段进行排序,也就是 ORDER BY 阶段 ,得到虚拟表 vt6 。
(七)最后在 vt6 的基础上,取出指定行的记录,也就是 LIMIT 阶段 ,得到最终的结果,对应的是虚拟表vt7 。
当然我们在写 SELECT 语句的时候,不一定存在所有的关键字,相应的阶段就会省略。
同时因为 SQL 是一门类似英语的结构化查询语言,所以我们在写 SELECT 语句的时候,还要注意相应的关键字顺序,所谓底层运行的原理,就是我们刚才讲到的执行顺序。


五:子查询

子查询指一个查询语句嵌套在另一个查询语句内部的查询,这个特性从MySQL 4.1开始引入。
SQL 中子查询的使用大大增强了 SELECT 查询的能力,因为很多时候查询需要从结果集中获取数据,或者需要从同一个表中先计算得出一个数据结果,然后与这个数据结果(可能是某个标量,也可能是某个集合)进行比较。

5.1 需求分析与问题解决:

5.1.1 实际问题:

53166820866547e9af4941ad08fba0c0.png

  1. #方式一:
  2. SELECT salary
  3. FROM employees
  4. WHERE last_name = 'Abel';
  5. SELECT last_name,salary
  6. FROM employees
  7. WHERE salary > 11000;
  8. #方式二:自连接
  9. SELECT e2.last_name,e2.salary
  10. FROM employees e1,employees e2
  11. WHERE e1.last_name = 'Abel'
  12. AND e1.`salary` < e2.`salary`
  13. #方式三:子查询
  14. SELECT last_name,salary
  15. FROM employees
  16. WHERE salary > (
  17. SELECT salary
  18. FROM employees
  19. WHERE last_name = 'Abel'
  20. );

5.1.2 子查询的基本使用:

子查询的基本语法结构:

784cc15048614b7997d1c2b62f3afd74.png

子查询(内查询)在主查询之前一次执行完成。
子查询的结果被主查询(外查询)使用 。

注意事项:

  • 子查询要包含在括号内
  • 将子查询放在比较条件的右侧
  • 单行操作符对应单行子查询,多行操作符对应多行子查询

5.1.3 子查询的分类:

分类方式1:

我们按内查询的结果返回一条还是多条记录,将子查询分为 单行子查询 、 多行子查询 。

单行子查询:

43d16b5162ea413c9e62894f8079b942.png

多行子查询:

7fa3373b27b647939206ed2b461abb20.png

分类方式2:

我们按内查询是否被执行多次,将子查询划分为 相关(或关联)子查询 和 不相关(或非关联)子查询 。
子查询从数据表中查询了数据结果,如果这个数据结果只执行一次,然后这个数据结果作为主查询的条件进行执行,那么这样的子查询叫做不相关子查询。
同样,如果子查询需要执行多次,即采用循环的方式,先从外部查询开始,每次都传入子查询进行查询,然后再将结果反馈给外部,这种嵌套的执行方式就称为相关子查询。

5.2 单行子查询:

5.2.1 单行比较操作符:

ddb2b08a6af0467babf8ea98c71691e7.png

5.2.2 HAVING 中的子查询:

  • 首先执行子查询。
  • 向主查询中的HAVING 子句返回结果。

5.2.4 CASE中的子查询:

在CASE表达式中使用单列子查询:

  1. #题目:显式员工的employee_id,last_name和location。其中,若员工department_id与location_id为1800的department_id相同,则location为’Canada’,其余则为’USA’。
  2. SELECT employee_id, last_name,
  3. (CASE department_id
  4. WHEN
  5. (SELECT department_id FROM departments
  6. WHERE location_id = 1800)
  7. THEN 'Canada' ELSE 'USA' END) location
  8. FROM employees;

5.2.5 子查询中的空值问题:

  1. SELECT last_name, job_id
  2. FROM employees
  3. WHERE job_id =
  4. (SELECT job_id
  5. FROM employees
  6. WHERE last_name = 'Haas');

子查询不返回任何行。

5.2.6 非法使用子查询:

  1. SELECT employee_id, last_name
  2. FROM employees
  3. WHERE salary =
  4. (SELECT MIN(salary)
  5. FROM employees
  6. GROUP BY department_id);

多行子查询使用单行比较符。

5.3 多行子查询:

  • 也称为集合比较子查询
  • 内查询返回多行
  • 使用多行比较操作符

5.3.1 多行比较操作符 :

0270c8d07caf43b9a52bef42967eba61.png

体会 ANY 和 ALL 的区别。

代码实例:

(一)题目:返回其它job_id中比job_id为‘IT_PROG’部门任一工资低的员工的员工号、姓名、job_id 以及salary

aae0f3f5cf2a4eec84c31ae358372feb.png

(二)题目:返回其它job_id中比job_id为‘IT_PROG’部门所有工资都低的员工的员工号、姓名、job_id以及salary

99f8f62cbfa5434fb043b1c88156eb08.png

5.3.3 空值问题 :

  1. SELECT last_name
  2. FROM employees
  3. WHERE employee_id NOT IN (
  4. SELECT manager_id
  5. FROM employees
  6. );

5.4 相关子查询 :

5.4.1 相关子查询执行流程:

如果子查询的执行依赖于外部查询,通常情况下都是因为子查询中的表用到了外部的表,并进行了条件关联,因此每执行一次外部查询,子查询都要重新计算一次,这样的子查询就称之为 关联子查询 。相关子查询按照一行接一行的顺序执行,主查询的每一行都执行一次子查询。

a5c8ae42bacd41479b6cf27cacbebb32.png

说明:子查询中使用主查询中的列。

5.4.2 EXISTS 与 NOT EXISTS关键字:

关联子查询通常也会和 EXISTS操作符一起来使用,用来检查在子查询中是否存在满足条件的行。
如果在子查询中不存在满足条件的行:条件返回 FALSE,继续在子查询中查找
如果在子查询中存在满足条件的行:不在子查询中继续查找,条件返回 TRUE
NOT EXISTS关键字表示如果不存在某种条件,则返回TRUE,否则返回FALSE。

题目:查询公司管理者的employee_id,last_name,job_id,department_id信息

  1. #方式一:
  2. SELECT employee_id, last_name, job_id, department_id
  3. FROM employees e1
  4. WHERE EXISTS ( SELECT *
  5. FROM employees e2
  6. WHERE e2.manager_id =
  7. e1.employee_id);
  8. #方式二:
  9. SELECT DISTINCT e1.employee_id, e1.last_name, e1.job_id, e1.department_id
  10. FROM employees e1 JOIN employees e2
  11. WHERE e1.employee_id = e2.manager_id;
  12. #方式三:
  13. SELECT employee_id,last_name,job_id,department_id
  14. FROM employees
  15. WHERE employee_id IN (
  16. SELECT DISTINCT manager_id
  17. FROM employees
  18. );

5.4.4 相关更新:

  1. UPDATE table1 alias1
  2. SET column = (SELECT expression
  3. FROM table2 alias2
  4. WHERE alias1.column = alias2.column);

使用相关子查询依据一个表中的数据更新另一个表的数据。

题目:在employees中增加一个department_name字段,数据为员工对应的部门名称:

  1. # 1)
  2. ALTER TABLE employees
  3. ADD(department_name VARCHAR2(14));
  4. # 2)
  5. UPDATE employees e
  6. SET department_name = (SELECT department_name
  7. FROM departments d
  8. WHERE e.department_id = d.department_id);

5.4.4 相关删除:

  1. DELETE FROM table1 alias1
  2. WHERE column operator (SELECT expression
  3. FROM table2 alias2
  4. WHERE alias1.column = alias2.column);

使用相关子查询依据一个表中的数据删除另一个表的数据。

题目:删除表employees中,其与emp_history表皆有的数据:

  1. DELETE FROM employees e
  2. WHERE employee_id in
  3. (SELECT employee_id
  4. FROM emp_history
  5. WHERE employee_id = e.employee_id);

发表评论

表情:
评论列表 (有 0 条评论,45人围观)

还没有评论,来说两句吧...

相关阅读

    相关 C++学习笔记)函数

      C++除了使用库函数,还可以自定义函数来完成一些功能。 函数可以分为有返回值的函数和无返回值的函数,没有返回值的通用格式 void 函数名(参数列表) \{ //