目录
[toc]
一、课程介绍
1.数据的统计分析:
汇总函数(全表范围),分组查询、HAVING子句
2.多表连接查询
内连接、外连接
3.子查询
嵌套查询:单行子查询,多行子查询;
WHERE子查询、FROM子查询、SELECT子查询
二、汇总函数
又叫聚合函数。
2.1 作用
简单的数据统计。
在查询分析中,应用广泛:可以对数据进行求和、求最大值和最小值、求平均值等。
特点:
- 默认情况下,统计范围是整个数据表。
- 返回的结果集中,只有一条记录。
汇总函数一共有5个,逐一介绍:
2.2 分类
1.SUM函数
1.用途:
SUM函数求和的对象只能是数字型。因为如果是字符型,统计结果为0;如果是日期型,统计结果是毫秒数相加。
2.案例演示:
SELECT SUM(sal) FROM t_emp WHERE deptno IN(10,20); # sal是整数型
结果如下:
2.MAX函数
1.用途:
求非空的最大值。
2.案例演示:
(1)查询10和20部门中,月收入最高的员工?
SELECT SUM(sal),MAX(sal+IFNULL(comm,0)) # MAX求最大值
FROM t_emp
WHERE deptno IN(10,20);
结果如下:
(2)查询员工名字最长的是几个字符?
SELECT MAX(LENGTH(ename)) # 与LENGTH()函数,打个配合
FROM t_emp;
结果如下:
3.MIN函数
与MAX函数正好相反。
1.用途:
求非空值的最小值
2.案例演示:
(1)求员工表中,员工编号最小的编号是?
SELECT MIN(empno)
FROM t_emp;
结果如下:
(2)求员工表中,雇佣日期最早的日期是?
SELECT MIN(hiredate)
FROM t_emp;
结果如下:
5.AVG函数
1.用途
求非空值的平均值。
对象只能是数字型。非0数字的平均值为0
2.案例演示
求员工表中的员工工资的平均值?
SELECT AVG(sal+IFNULL(comm,0)) AS avg # 用AS给起个别名
FROM t_emp;
结果如下:
6.COUNT函数
1.用途:
求记录的数量。
有两种写法:
- COUNT(*)-------------求记录的数量,全部都有
- COUNT(comm)------求记录的数量,只统计非空值的记录
2.案例演示:
求员工表中,总的记录数,和有佣金的记录数?
SELECT
COUNT(*),COUNT(comm)
FROM t_emp;
结果如下:
2.3 练习
1.查询10和20部门中,底薪超过2000元并且工龄超过15年的员工人数?
分析:有3个条件:部门、底薪、工龄
SELECT
COUNT(*)
FROM t_emp
WHERE deptno IN(10,20) AND sal>=2000 AND DATEDIFF(NOW(),hiredate)/365>=15;
结果如下:
2.查询1985年以后入职的员工,底薪超过公司平均底薪的员工数量?
是不是什么条件,都可以写进WHERE子句中?
不是,汇总函数永远不能放进WHERE子句中。
因为汇总函数执行的前提是,必须先划定清晰的数据范围。
同时,WHERE子句的核心使命就是根据给定条件,限定数据范围。
所以,汇总函数只能放在WHERE子句之后。
即,上述的五大汇总函数,只能放在SELECT子句中。
SELECT
COUNT(*)
FROM t_emp
WHERE hiredate>="1985-01-01" AND sal>AVG(sal); # ❌
结果如下:
三、分组查询
上节讲的是汇总函数,默认统计的是全表范围的数据,配合WHERE子句,可以缩小统计的范围。
但是,并不总能满足日常开发需求。因为很多时候,需要对数据进行分组之后,才进行汇总统计。
3.1 GROUP BY子句
通过一定规则,将数据集划分成若干小区域,然后对每个小区域使用五大汇总函数进行汇总处理。
SELECT
deptno,ROUND(AVG(sal)) # 再使用五大汇总函数,汇总处理
FROM t_emp
GROUP BY deptno; # 先按照部门编号分组
结果如下:
3.2 逐级分组
上面的只是一级分组,根据实际情况,也可以进行二级分组。
1.案例分析:
查询每个部门里,每种职位的人员数量和平均底薪?
一级分组:按照部门先分组
二级分组:按照职位再分组
SELECT
deptno,job,COUNT(*),AVG(sal)
FROM t_emp
GROUP BY deptno,job
ORDER BY deptno;
结果如下:
3.3 GROUP BY子句对SELECT子句的规定
若查询语句中有GROUP BY子句,那么SELECT子句中的内容就必须遵守规定:
SELECT子句只可以包含汇总函数,或只可以包含GROUP BY子句的分组列。
之所以有上述规定,是因为为了保持数据的匹配性。
3.4 功能:对分组的结果集再次做汇总计算
上面的是对分组后的数据,进行汇总处理;接下来,是对前面得到的数据结果集,进一步进行二次汇总处理。
WITH ROLLUP
3.5 GROUP_CONCAT函数:非分组字段合并成一条记录
1.用途:
可以把分组查询中的多个非分组字段合并成一条记录
2.案例应用:
查询每个部门中底薪超过2000元的人数和员工姓名?
SELECT deptno,COUNT(*),GROUP_CONCAT(ename) # 此处员工姓名就是非分组字段
FROM t_emp
WHERE sal>=2000
GROUP BY deptno;
注:这里的CONCAT是“连接”的意思
3.6 子句的执行顺序
FROM--->WHERE--->GROUP BY--->SELECT--->ORDER BY--->LIMIT
GROUP BY应该紧缩WHERE其后,只有根据条件WHERE缩小了范围后,再分组分析。
四、HAVING 子句
4.1 必要性
1.常见问题
因为有些条件查询,用GROUP BY子句并不是总能满足实际开发需求。
SELECT deptno
FROM t_emp
WHERE AVG(sal)>=2000
GROUP BY deptno;
--->
❌
上述问题出现在,在WHERE子句中,出现了汇总函数。两者的定位相互矛盾。
此时,为了装得下包含汇总函数的条件,HAVING子句,应运而生。
2.解决办法
将包含汇总函数的条件,通过HAVING关键字,放在GROUP BY子句中。
SELECT deptno
FROM t_emp
GROUP BY deptno HAVING AVG(sal)>=2000; # 先分好组,再用汇总函数计算平均值
4.2 特点
(1)HAVING子句跟WHERE子句功能相同,都是用来条件筛选。但是,因为存在汇总函数以及执行顺序的原因,HAVING子句就必须依赖GROUP BY子句,不能单独存在。
(2)HAVING子句中,拿汇总函数跟具体的数据进行条件判断是可以的,但是,拿汇总函数跟某个字段进行条件判断,就不行了。(只能用表连接)
4.3 案例应用
查询每个部门中,1982年以后入职的员工超过2个人的部门编号?
SELECT deptno
FROM t_emp
WHERE hiredate>="1982-01-01"
GROUP BY deptno HAVING COUNT(*)>=2; # 将包含汇总函数的条件,放在GROUP BY子句中
4. 4 简化写法
(1)在GROUP BY子句中,用1代替SELECT子句中第一列进行分组
- 正常写法:
SELECT deptno,COUNT(*)
FROM t_emp
GROUP BY deptno;
- 简化写法:
SELECT deptno,COUNT(*)
FROM t_emp
GROUP BY 1; # 这里的1代表SELECT子句中第一列:deptno
(2)
WHERE子句是不能被HAVING子句替代的,虽然表面上两者功能上略有相同,但是,两者的实质定位、执行顺序是有差异的。
- 虽然语法不报错、结果也对,但是不没意义的写法:
SELECT deptno,COUNT(*)
FROM t_emp
GROUP BY 1 HAVING deptno IN(10,20);
- 推荐的正确的写法:
SELECT deptno,COUNT(*)
FROM t_emp
WHERE deptno IN(10,20) # 普通的条件查询,让WHERE打冲锋
GROUP BY 1;
五、多张表连接查询
上面讲的是从一张表中来分析数据,接下来学习从多张表中来分析数据。这也更符合实际的开发场景。
比如,一个逻辑库中有上百张数据表,一个查询语句经常会查询七八张数据表,甚至十几张数据表。工作必备技能。
下面的操作,都是在FROM子句中。
5.1 指定连接条件
1.语法
从多张表中提取数据,必须指定关联的条件。如果没有关联条件,就会出现无条件连接,两张表的数据就会交叉连接,产生笛卡尔积。这不符合现实逻辑。
(1)没有关联条件的多张表连接:
只有JOIN
(2)指定关联条件的多张表连接:
JOIN-ON
ON的作用是,当后面写的条件满足时,两个表才连接成功。“合体”生成一整张临时表。
2.案例应用
SELECT e.empno,e.ename,d.dname # 查询的是两张表
FROM t_emp e JOIN t_dept d ON e.deptno=d.deptno;
5.2 表连接的分类
分两种:内连接、外连接
- 内连接:结果集里,只保留符合连接条件的记录
- 外连接:不管符不符合连接条件,记录都保留在结果集里
5.3 内连接
1.特点
- 结果集里,只保留符合连接条件的记录
- 最常见的一种表连接。用于查询多张数据表同时符合的记录:
2.多种语法形式
(1)标准写法1:
(2)其他写法2:
将上面的连接条件,放到WHERE子句中:
(3)其他写法3:
在写法2的基础上,去掉JOIN关键字,用逗号将表名分开:
3.案例演示
(1)标准写法1:
SELECT e.empno,e.ename,d.dname # 查询的是两张表
FROM t_emp e JOIN t_dept d
ON e.deptno=d.deptno;
(2)其他写法2:
SELECT e.empno,e.ename,d.dname # 查询的是两张表
FROM t_emp e JOIN t_dept d
WHERE e.deptno=d.deptno;
(3)其他写法3:
SELECT e.empno,e.ename,d.dname # 查询的是两张表
FROM t_emp e,t_dept d
WHERE e.deptno=d.deptno;
多张表的连接查询很重要,后续工作会经常使用,接下来做一些例题,以巩固语法。
4.课堂例题1:查询每个员工的工号、姓名、部门名称、底薪、职位、工资等级?
分析:
- 涉及三张表:员工表、部门表、工资等级表。
- 员工表与部门表有相同的字段,但是员工表与工资表没有相同字段,不过有逻辑关系的字段
SELECT
e.empno,e.ename,d.dname,e.sal,e.job,s.grade
FROM t_emp e
JOIN t_dept d ON e.deptno=d.deptno # 员工表与部门表,有相同的字段
JOIN t_salgrade s ON e.sal BETWEEN s.losal AND s.hisal; # 员工表与工资表,没有相同字段,不过有逻辑关系的字段
小结:内连接的两张数据表,不一定必须有同名字段,只要字段之间符合逻辑关系就可以。
5.课堂例题2:查询与SCOTT相同部门的员工都有谁?
(1)先查一下该员工所处的部门是哪个:
SELECT
deptno
FROM t_emp
WHERE ename="SCOTT";
(2)把上面的SQL语句括起来,把上面的结果作为条件,放在WHERE子句中进行筛选:
SELECT ename
FROM t_emp
WHERE deptno=
(SELECT deptno FROM t_emp WHERE ename="SCOTT");
(3)上面的结果集里,有SCOTT,这是不合理的。应该去掉他,改为:
SELECT ename
FROM t_emp
WHERE deptno=
(SELECT deptno FROM t_emp WHERE ename="SCOTT")
AND ename!="SCOTT"; # 剔除掉自己
这个写法是子查询,这很符合我们的思考习惯,但是,数据库执行的效率是很低的。
因为,每运行一条员工表中的记录,都要去重新运行一遍WHERE子句中的deptno=(SELECT deptno FROM t_emp WHERE ename="SCOTT")
。
员工表中有1000条记录,WHERE子句及内容,就要运行1000遍。
(4)改造后的正确写法:
SELECT
e2.ename
FROM t_emp e1 JOIN t_emp e2 ON e1.deptno=e2.deptno #因为一张表与它的“分身”,是完全相同的,也就一定会有同名字段
WHERE e1.ename="SCOTT" AND e2.ename!="SCOTT";
小结:相同的数据表也可以做表连接。
还是没有明白?尤其是WHERE子句中的条件
5.4 巩固练习
1.练习1.查询底薪超过公司平均底薪的员工信息?
分析:
- 两个不知道:公司平均底薪是多少?谁超过了这个值?
- 用表连接做:
写法1:
看似正常的SQL语句,为什么是语法错误的呢?
SELECT e2.empno,e2.ename,e2.sal
FROM t_emp e1 JOIN t_emp e2 ON e2.sal>=AVG(e1.sal); #自己与自己做表连接是可以的,但是“娇生惯养”的汇总函数这样出现,就不合理
上面的ON的功能同WHERE子句。所以,就恍然大悟:WHERE子句中不能有汇总函数!
ON同理,连接条件中出现汇总函数,也是错误的。
因为,汇总函数就只能在WHERE子句之后,就更不应该放进FROM子句中了。
写法2:
(1)先求出公司的平均底薪:
SELECT
AVG(sal) # 汇总函数,的结果是一条记录
FROM t_emp;
(2)将上面查询后的结果集视为一张新的表,写在FROM子句中,通过JOIN-ON,与原表做表连接:
SELECT e.empno,e.ename,e.sal
FROM t_emp e
JOIN (SELECT AVG(sal) avg FROM t_emp) t ON e.sal>=t.avg; # 把汇总函数及其范围包裹起来,其结果通过列别名形式见天日
小结:数据集也可以作为一张“表”,来跟其他表进行连接
2.练习2.查询RESEARCH部门的人数、最高底薪、最低底薪、平均底薪、平均工龄?
SELECT
COUNT(*),MAX(e.sal),MIN(e.sal),AVG(e.sal),FLOOR(AVG(DATEDIFF(NOW(),e.hiredate)/365))
FROM t_emp e JOIN t_dept d ON e.deptno=d.deptno
WHERE d.dname="RESEARCH";
拓展:
工龄是不能四舍五入的。应该往下舍位。
SELECT FLOOR(28.9); # 强制往下舍位 地板
SELECT CEIL(1.1); # 强制往上进位 天花板
--->
28
2
3.练习3.查询每种职业的最高工资、最低工资、平均工资、最高工资等级、最低工资等级?
分析:员工表、工资等级表
SELECT e.job,
MAX(e.sal+IFNULL(e.comm,0)),
MIN(e.sal+IFNULL(e.comm,0)),
AVG(e.sal+IFNULL(e.comm,0)),
MAX(s.grade),
MIN(s.grade)
FROM t_emp e
JOIN t_salgrade s ON (e.sal+IFNULL(e.comm,0)) BETWEEN s.losal AND s.hisal
GROUP BY e.job;
5.5 外连接
1.必要性
使用内连接,有时候会漏掉信息:
- 因为内连接只会显示符合条件的记录,而外连接不管符不符合连接条件,记录都保留在结果集里
- 查询条件是两个表的部门编号是相同的,但是陈浩没有部门编号。
2.语法
LEFT JOIN
必须保留左表所有的记录,然后跟右表去连接。如果右表有符合连接条件的记录,那么正常连接;如果右表没有符合连接条件的记录,右表就出一个NULL值跟左表连接。
(1)漏掉陈浩的内连接的写法:
SELECT e.empno,e.ename,d.dname
FROM t_emp e JOIN t_dept d ON e.deptno=d.deptno;
(2)包含陈浩的外连接的写法:
SELECT e.empno,e.ename,d.dname
FROM t_emp e LEFT JOIN t_dept d ON e.deptno=d.deptno; # 外连接
3.右外连接
最常用的是上面的外连接,即左外连接。其实,还有一种连接叫右外连接,差不多:
- 因为是右外连接,所以全部保留的是右边的表。
SELECT e.empno,e.ename,d.dname
FROM t_dept d RIGHT JOIN t_emp e ON e.deptno=d.deptno; # 要把全部保留的员工表放在右边
5.6 巩固练习
1.查询每个部门的名称和部门的人数是?
特殊之处在于:部门表中有个部门叫40部门,但是,员工表中没有一个员工是40部门的。
因此,表连接操作时,就必须保留部门表的40部门。
外连接,当仁不让。
(1)错误写法
SELECT d.dname,COUNT(*)
FROM t_dept d LEFT JOIN t_emp e ON d.deptno=e.deptno
GROUP BY d.deptno;
不是部门表中的40部门,没有人是这个部门的吗,怎么是1呢?
这是因为左外连接中,右边的表的字段如果没有记录,就用NULL代替,NULL也是一条记录。
(2)正确写法
SELECT d.dname,COUNT(e.deptno) # 将员工表中的部门编号字段放进来
FROM t_dept d LEFT JOIN t_emp e ON d.deptno=e.deptno
GROUP BY d.deptno;
即,分组的时候,是用部门表的四个部门名;统计各部门的员工数量时,是用员工表的部门编号。
因为,前者刚好是4个部门名,后者是只有3个部门名的员工数量。
2.查询每个部门的名称和部门的人数?如果没有部门的员工,部门名称用NULL来代替显示。
(1)错误写法
SELECT d.dname,COUNT(e.deptno)
FROM t_dept d
RIGHT JOIN t_emp e ON d.deptno=e.deptno # 改为右连接
GROUP BY d.deptno;
(2)错误写法
(SELECT d.dname,COUNT(e.deptno)
FROM t_dept d
LEFT JOIN t_emp e ON d.deptno=e.deptno # 左连接
GROUP BY d.deptno)
UNION # 用UNION关键字,进行查询语句的暴力连接
(SELECT d.dname,COUNT(e.deptno)
FROM t_dept d
RIGHT JOIN t_emp e ON d.deptno=e.deptno # 右连接
GROUP BY d.deptno);
之所以这么显示,是因为陈浩是属于NULL部门的,右边的数字应该是1。
(3)正确写法
(SELECT d.dname,COUNT(e.deptno)
FROM t_dept d
LEFT JOIN t_emp e ON d.deptno=e.deptno
GROUP BY d.deptno)
UNION
(SELECT d.dname,COUNT(*) # 改为星号
FROM t_dept d
RIGHT JOIN t_emp e ON d.deptno=e.deptno
GROUP BY d.deptno);
还是没有明白改为星号的区别?
3.查询每名员工的编号、姓名、部门名称、月薪、工资等级、工龄、上司编号、上司姓名、上司部门?
分析:员工表、部门表、工资等级表,上司的信息,用表连接去做
(1)我的写法:
SELECT e.empno,e.ename,d.dname,
(sal+IFNULL(comm,0)),
(SELECT s.grade FROM t_emp e JOIN t_salgrade s ON e.sal BETWEEN s.losal AND s.hisal),
FLOOR(DATEDIFF(NOW(),hiredate)/365),
e.mgr
? # 上司的编号、姓名、部门,咋整?
FROM t_emp e
LEFT JOIN t_dept d ON e.deptno=d.deptno;
SELECT empno,mgr FROM t_emp # ❌一个新表
SELECT e.ename
FROM t_emp e1 JOIN t_emp e2 ON e1.empno=e2.empno # ❌
(2)正确的写法:
SELECT e.empno,e.ename,d.dname,
(e.sal+IFNULL(e.comm,0)), # 注意1
s.grade, # 注意2
FLOOR(DATEDIFF(NOW(),hiredate)/365),
t.empno AS mgrno,t.ename AS mname,t.dname AS mdname # 注意3:上司的编号、姓名、部门
FROM t_emp e
LEFT JOIN t_dept d ON e.deptno=d.deptno
LEFT JOIN t_salgrade s ON e.sal BETWEEN s.losal AND s.hisal
LEFT JOIN (SELECT e1.empno,e1.ename,d1.dname FROM t_emp e1 JOIN t_dept d1 ON e1.deptno=d1.deptno) t
ON e.mgr=t.empno;
# 关联条件:员工表的mgr与上司的结果集中的上司编号必须一致
最终结果:
其中的关键步骤是:
因为员工既是员工,又是别人的上司。所以,得出员工的编号、姓名、部门结果集,也是上司的编号、姓名、部门的结果集:
SELECT e1.empno,e1.ename,d1.dname
FROM t_emp e1 JOIN t_dept d1 ON e1.deptno=d1.deptno;
5.7 UNION关键字
UNION
关键字:将多个查询语句的结果集进行合并。
使用要求:每个查询语句得到的结果集,其字段的个数、字段的名称,必须是相同的
5.8 ON子句和WHERE子句在外连接上的区别
在内连接上,ON子句和WHERE子句是没有区别的;
但是,在外连接上,两者有很大的区别。
1.查询10部门的员工编号、员工姓名、部门名称?
(1)用ON子句(还是在FROM子句中):
SELECT e.empno,e.ename,d.dname
FROM t_emp e
LEFT JOIN t_dept d ON e.deptno=d.deptno AND e.deptno=10;
发现了差错:有些不是10部门的员工,也被显示出来。
因为,用的是左外连接,即不管关联条件是否成立,左边的员工表的记录强制全部保留。
(2)用WHERE子句:
SELECT e.empno,e.ename,d.dname
FROM t_emp e
LEFT JOIN t_dept d ON e.deptno=d.deptno
WHERE e.deptno=10; # 所以,一般的条件,还是让WHERE冲锋陷阵吧,使命就是缩小范围
成功显示了10部门的员工编号、员工姓名、部门名称,没有多余:
五、子查询
子查询确实效率低下,但也不是所有的子查询都不推荐。
5.1 概念
子查询是一种在查询中又嵌套另一个查询的语句。
子查询可以写在三个地方:SELECT子句、FROM子句、WHERE子句。其中,只推荐FROM子句中的子查询。
5.2 WHERE子句中的子查询
1.特点
最简单、最易理解,但是执行效率低。
2.WHERE子句中的子查询写法:
查询底薪超过公司平均底薪的员工的信息?
SELECT empno,ename,sal
FROM t_emp
WHERE sal>=(SELECT AVG(sal) FROM t_emp);
5.3 FROM子句中的子查询
前面其实已经用过了。
1.特点
只会执行1次,查询效率高。
本质还是因为,FROM子句中的所有成员,都是只执行一次的。
2.FROM子句中的子查询写法:
查询底薪超过公司的部门平均底薪的员工的信息?
(1)只子查询得到的结果集:
SELECT deptno,AVG(sal) AS avg
FROM t_emp
GROUP BY deptno
(2)将上述结果集作为一张“表”,加入到FROM子句中:
SELECT empno,ename,e.sal,t.avg
FROM t_emp e
JOIN (SELECT deptno,AVG(sal) AS avg FROM t_emp GROUP BY deptno) t
ON e.deptno=t.deptno
AND e.sal>=t.avg;
5.4 SELECT子句中的子查询
1.特点
同WHERE子句中子查询,每输出一条记录,都要执行一次,查询效率很低。不推荐。
2.案例演示
SELECT
e.empno,e.ename,
(SELECT dname FROM t_dept WHERE deptno=e.deptno)
FROM t_emp e;
5.5 单行子查询、多行子查询
以子查询的结果集的数量,来划分子查询:
1.单行子查询
结果集只有1条记录。
2.多行子查询
结果集有多行记录。
只能出现在WHERE子句、FROM子句中。
3.案例应用
用子查询,查找FORD和MARTIN两个人的部门同事?
- 名字是FORD和MARTIN的员工,所在的部门编号是多少:
SELECT deptno FROM t_emp WHERE ename IN("FORD","MARTIN");
- 注意下运算符IN,逻辑上的或关系:
- 正确的写法:
SELECT ename
FROM t_emp
WHERE
deptno IN (SELECT deptno FROM t_emp WHERE ename IN("FORD","MARTIN")) # 相当于WHERE deptno IN (20,30)
AND ename NOT IN ("FORD","MARTIN"); # IN运算符,真好使啊
六、WHERE子句中的多行子查询
当使用WHERE子句时,可以用以下关键字来处理多行表达式结果集的条件判断:
IN、ALL、ANY、EXISTS
6.1 ALL、ANY关键字
查询比FORD和MARTIN底薪都高的员工信息有谁?
因为结果集中,返回了多条记录。
- 先查询出FORD和MARTIN的底薪:
SELECT sal FROM t_emp WHERE ename IN("FORD","MARTIN");
- 将上述的结果集放进WHERE子句中
SELECT ename
FROM t_emp
WHERE sal>=ALL(SELECT sal FROM t_emp WHERE ename IN("FORD","MARTIN")) #相当于WHERE sal>=ALL(1250,3000)
AND ename NOT IN ("FORD","MARTIN");
6.2 EXISTS关键字
1.语法
把原来在子查询之外的条件判断,写到子查询里面:
只要子查询的结果集不为空,就代表条件符合了;如果为空,就代表条件不符合。
2.案例应用
查询工资等级是3级或者4级的员工信息?
SELECT empno,ename,sal
FROM t_emp
WHERE EXISTS(
SELECT * FROM t_salgrade WHERE sal BETWEEN losal AND hisal AND grade IN (3,4)
);
Comments | NOTHING