MySQL学习笔记(六)

目录

一:聚合函数

二:GROUP BY 

三: HAVING

四: SELECT的执行过程

五:子查询


一:聚合函数

1.1 什么是聚合函数:

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

MySQL学习笔记(六)_第1张图片

 1.2 聚合函数类型:

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

1.3 聚合函数语法:

MySQL学习笔记(六)_第2张图片

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

1.4 AVG和SUM函数:

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

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

1.5 MIN和MAX函数:

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

SELECT MIN(hire_date), MAX(hire_date)
FROM employees;

1.6 COUNT函数:

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

SELECT COUNT(*)
FROM employees
WHERE department_id = 50;

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

SELECT COUNT(commission_pct)
FROM   employees
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 值的行。

二:GROUP BY 

2.1 基本使用:

MySQL学习笔记(六)_第3张图片

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

SELECT column, group_function(column)
FROM table
[WHERE condition]
[GROUP BY group_by_expression]
[ORDER BY column];

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

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

SELECT   department_id, AVG(salary)
FROM     employees
GROUP BY department_id ;

MySQL学习笔记(六)_第4张图片

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

SELECT   AVG(salary)
FROM     employees
GROUP BY department_id ;

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

2.2 使用多个列分组:

#	需求:查询各个department_id,job_id的平均工资
#方式一:
SELECT department_id,job_id,AVG(salary)
FROM employees
GROUP BY department_id,job_id;
#方式二:
SELECT job_id,department_id,AVG(salary)
FROM employees
GROUP BY job_id,department_id;		

2.3 GROUP BY中使用WITH ROLLUP  :

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

SELECT department_id,AVG(salary)
FROM employees
WHERE department_id > 80
GROUP BY department_id WITH ROLLUP;

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

三: HAVING

3.1 基本使用:

MySQL学习笔记(六)_第5张图片

 过滤分组:HAVING子句
1. 行已经被分组。
2. 使用了聚合函数。
3. 满足HAVING 子句中条件的分组将被显示。
4. HAVING 不能单独使用,必须要跟 GROUP BY 一起使用。

SELECT   department_id, MAX(salary)
FROM     employees
GROUP BY department_id
HAVING   MAX(salary)>10000 ;

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

#查询部门id以及部门最高工资大于10000
SELECT department_id,MAX(salary)
FROM employees
GROUP BY department_id
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 则需要先把结果集准备好,也就是用未被筛选的数据集进行关联,然后对这个大的数据集进行筛选,这样占用的资源就比较多,执行效率也较低。
 

MySQL学习笔记(六)_第6张图片

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

即:

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

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

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

四: SELECT的执行过程

4.1 查询的结构:

#方式1(sql92):
SELECT ...,....,...
FROM ...,...,....
WHERE 多表的连接条件
AND 不包含组函数的过滤条件
GROUP BY ...,...
HAVING 包含组函数的过滤条件
ORDER BY ... ASC/DESC
LIMIT ...,...
#方式2(sql99):
SELECT ...,....,...
FROM ... JOIN ... 
ON 多表的连接条件
JOIN ...
ON ...
WHERE 不包含组函数的过滤条件
AND/OR 不包含组函数的过滤条件
GROUP BY ...,...
HAVING 包含组函数的过滤条件
ORDER BY ... ASC/DESC
LIMIT ...,...
#其中:
#(1)from:从哪些表中筛选
#(2)on:关联多表查询时,去除笛卡尔积
#(3)where:从表中筛选的条件
#(4)group by:分组依据
#(5)having:在统计结果中再次筛选
#(6)order by:排序
#(7)limit:分页

4.2 SELECT执行顺序:

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

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

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

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

MySQL学习笔记(六)_第7张图片

SELECT DISTINCT player_id, player_name, count(*) as num # 顺序 5
FROM player JOIN team ON player.team_id = team.team_id # 顺序 1
WHERE height > 1.80 # 顺序 2
GROUP BY player.team_id # 顺序 3
HAVING num > 2 # 顺序 4
ORDER BY num DESC # 顺序 6
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 实际问题:

MySQL学习笔记(六)_第8张图片

#方式一:
SELECT salary
FROM employees
WHERE last_name = 'Abel';
SELECT last_name,salary
FROM employees
WHERE salary > 11000;

#方式二:自连接
SELECT e2.last_name,e2.salary
FROM employees e1,employees e2
WHERE e1.last_name = 'Abel'
AND e1.`salary` < e2.`salary`

#方式三:子查询
SELECT last_name,salary
FROM employees
WHERE salary > (
 SELECT salary
 FROM employees
 WHERE last_name = 'Abel'
 );

5.1.2 子查询的基本使用:

子查询的基本语法结构:

MySQL学习笔记(六)_第9张图片

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

注意事项:

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

5.1.3 子查询的分类:

分类方式1:

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

单行子查询:

MySQL学习笔记(六)_第10张图片

多行子查询:

MySQL学习笔记(六)_第11张图片

分类方式2:

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

5.2 单行子查询:

5.2.1 单行比较操作符:

MySQL学习笔记(六)_第12张图片

5.2.2 HAVING 中的子查询:

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

5.2.4 CASE中的子查询:

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

#题目:显式员工的employee_id,last_name和location。其中,若员工department_id与location_id为1800的department_id相同,则location为’Canada’,其余则为’USA’。

SELECT employee_id, last_name,
       (CASE department_id
       WHEN
             (SELECT department_id FROM departments
     WHERE location_id = 1800)           
       THEN 'Canada' ELSE 'USA' END) location
FROM   employees;

5.2.5 子查询中的空值问题:

SELECT last_name, job_id
FROM   employees
WHERE job_id =
               (SELECT job_id
                 FROM   employees
                 WHERE last_name = 'Haas');

子查询不返回任何行。

5.2.6 非法使用子查询:

SELECT employee_id, last_name
FROM   employees
WHERE salary =
               (SELECT   MIN(salary)
                 FROM     employees
                 GROUP BY department_id);

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

5.3  多行子查询:

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

5.3.1 多行比较操作符 :

MySQL学习笔记(六)_第13张图片

 体会 ANY 和 ALL 的区别。

代码实例:

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

MySQL学习笔记(六)_第14张图片

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

MySQL学习笔记(六)_第15张图片

5.3.3 空值问题 :

SELECT last_name 
FROM employees 
WHERE employee_id NOT IN (
                          SELECT manager_id 
                          FROM employees 
                          );

5.4 相关子查询 :

5.4.1 相关子查询执行流程:

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

MySQL学习笔记(六)_第16张图片

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

5.4.2 EXISTS 与 NOT EXISTS关键字:

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

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

#方式一:
SELECT employee_id, last_name, job_id, department_id
FROM   employees e1
WHERE EXISTS ( SELECT *
                 FROM   employees e2
                 WHERE e2.manager_id = 
                       e1.employee_id);
#方式二:
SELECT DISTINCT e1.employee_id, e1.last_name, e1.job_id, e1.department_id
FROM   employees e1 JOIN employees e2
WHERE e1.employee_id = e2.manager_id;
#方式三:
SELECT employee_id,last_name,job_id,department_id
FROM employees
WHERE employee_id IN (
     SELECT DISTINCT manager_id
     FROM employees
     
     );

5.4.4 相关更新:

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

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

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

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

5.4.4 相关删除:

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

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

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

DELETE FROM employees e
WHERE employee_id in  
           (SELECT employee_id
            FROM   emp_history 
            WHERE employee_id = e.employee_id);


 

你可能感兴趣的:(学习,数据库)