前面我们讲解的mysql表的查询都是对一张表进行查询,在实际开发中这远远不够。
对于CURD之一的查找,他作为最重要的操作,仅仅在一张表之中查是不够的,还需要在多表之间进行查询,复合查询就是解决多表查询的问题。
一.基本查询回顾
- 查询工资高于500或岗位为MANAGER的雇员,同时还要满足他们的姓名首字母为大写的J
- 按照部门号升序而雇员的工资降序排序
- 使用年薪进行降序排序
年薪=工资*12 奖金。而一些部门奖金为空,加起来后的结果自然也为空,故需要ifnull判断是否为空,若为空,则替换成0
- 显示工资最高的员工的名字和工作岗位
此时,可以先找到最高的工资,然后通过这个工资找到对应的ename。但这种方式较为鸡肋,需要查两次,因此选择select嵌套的方式进行查找。先找等号右侧括号里的,然后再返回给外面。
- 显示工资高于平均工资的员工信息
同样先将此信息分成两部分:一是找到高于平均工资的sal,二是通过此sal的匹配,找到对应的ename。
- 显示每个部门的平均工资和最高工资
此时显示的是每个部门,而不是全公司,所以一定是要按照部门进行分组的。按照谁分组,查询时就可以将谁显示,因为这样相同的值可以压缩。
平均工资小数过多,用format函数将小数控制在两位:
- 显示平均工资低于2000的部门号和它的平均工资
我们先将其分解:分组部门先找到平均工资低于2000的avg(sal),通过这个avg(sal)找到对应的部门号,存在分组的条件用having。
- 显示每种岗位的雇员总数,平均工资
二.多表查询
实际开发中往往数据来自不同的表,所以需要多表查询。本节我们用一个简单的公司管理系统,有三张表EMP,DEPT,SALGRADE来演示如何进行多表查询。
将三个表以笛卡尔积的形式合并会得到一个包含三个表的全部信息的表。
但是为了去掉不对的组合,就需要where控制emp.deptno=dept.deptno
- 显示部门号为10的部门名,员工名和工资
- 显示各个员工的姓名,工资,及工资级别
三.自连接
1. 自连接的笛卡尔积
不同的表可以做笛卡尔积,那么同一张表也可以做笛卡尔积。自连接就是指在同一张表连接查询。
直接连接是不对的:
但是我们可以将其分别重命名,进行区分:
可以发现,重命名可以在from后出现,换句话说,重命名可以在任何地方出现。这样就完成了salgrade自己与自己的笛卡尔积。
2. 自连接案例
什么时候需要自连接呢?
当需要在同一张表通过一个字段找另一个字段,再通过另一个字段找到它对应的信息时,就需要进行自连接。
即拿emp表举例,显示员工FORD的上级领导的编号和姓名(mgr是员工领导的编号–empno)
那么此时就需要两步,先根据员工名FORD找到对应的mgr,再通过mgr与empno的对应,找到empno对应的编号和姓名。
此时,就需要将emp进行自连接,并进行查询:
代码语言:javascript复制select e2.empno, e2.ename from emp e1, emp e2 where e1.ename='FORD' and e1.mgr=e2.empno;
当然,通过子查询的方式也可以进行查找
先通过名字查询对应的mgr,再让empno=满足条件的mgr。
代码语言:javascript复制select empno, ename from emp where empno=(select mgr from emp where ename='FORD');
四.子查询
子查询是指嵌入在其他sql语句中的select语句,也叫嵌套查询。
1. 单行子查询
返回一行记录的子查询
就像刚刚所演示的子查询式的查找,在这里我们同样的展示一下:
- 显示SMITH同一部门的员工
select * from emp where deptno = (select deptno from emp where ename='SMITH');
一样的道理,先通过SMITH找到对应的deptno,得到的这个deptno被当做约束条件查找对应的同一部门的员工。
2. 多行子查询
返回多行记录的子查询。即我们通过一个条件去寻找,能同时找到多个满足条件的数据,只要想要的数据在这多个数据中就显示。
- **in关键字;**查询和10号部门的工作岗位相同的雇员的名字,岗位,工资,部门号,但是不包含10自己的
select ename, job, sal, deptno from emp where job in(select distinct job from emp where deptno=10) and deptno <> 10;
- **all关键字;**显示工资比部门30的所有员工的工资高的员工的姓名、工资和部门号
select ename, sal, deptno from emp where sal > all(select sal from emp where deptno=30);
不用all,通过聚合函数max同样也可以满足要求
代码语言:javascript复制select ename, sal, deptno from emp where sal > (select max(sal) from emp group by deptno having deptno=30);
- **any关键字;**显示工资比部门30的任意员工的工资高的员工的姓名、工资和部门号(包含自己部门的员工)
只要存在比部门30的任意员工工资高的,就显示。
代码语言:javascript复制select ename, sal, deptno from emp where sal > any(select sal from emp where deptno=30);
3. 多列子查询
单行子查询是指子查询只返回单列,单行数据;多行子查询是指返回单列多行数据,都是针对单列而言的,而多列子查询则是指查询返回多个列数据的子查询语句。
即多个字段同时匹配,用括号将多个字段括起来,然后进行比较。
案例:查询和SMITH的部门和岗位完全相同的所有雇员,不含SMITH本人
代码语言:javascript复制select * from emp where (deptno, job) = (select deptno, job from emp where ename='SMITH') and ename <> 'SMITH';
4. 在from子句中使用子查询
子查询语句出现在from子句中。这里要用到数据查询的技巧,把一个子查询当做一个临时表使用。即我们无论从什么时候通过条件或者查询所得到的的间接的表,都可以看做真正的表进行查询,因为,MySQL下一切皆表。
案例
- 显示每个高于自己部门平均工资的员工的姓名、部门、工资、平均工资
对于每一个部门,显示的是高于自己部门平均工资的员工。
首先,将自己部门的平均工资筛选出来。
代码语言:javascript复制select deptno, avg(sal) from emp group by deptno;
将上一步的结果看成一张表,与emp表进行笛卡尔积,做完笛卡尔积,每一个员工根据部门的筛选,就都能对应自己部门的平均工资。
代码语言:javascript复制select * from emp, (select deptno, avg(sal) from emp group by deptno) tmp where emp.deptno = tmp.deptno;
接下来,就变成了普通的单表查询了,只需要在上面的这样表中,找到sal > avg(sal)的信息就可以了。
代码语言:javascript复制select ename, emp.deptno from emp, (select deptno, avg(sal) myavg from emp group by deptno) tmp where emp.deptno = tmp.deptno and emp.sal > tmp.myavg;
这也就得到了最终的结果。
若要显示这些人的办公地点,则只需要把上面的表与dept进行笛卡尔积,并通过dept一一对应,就可以得到最终结果
代码语言:javascript复制select t1.ename, dept.loc, t1.deptno from dept, (select ename, emp.deptno from emp, (select deptno, avg(sal) myavg from emp group by deptno) tmp where emp.deptno = tmp.deptno and emp.sal > tmp.myavg) t1 where t1.deptno = dept.deptno;
所以,面对非常复杂的查询问题,都可以将其进行分解,因为复杂的问题也都是由简单的问题复合而成的。
- 查找每个部门工资最高的人的姓名、工资、部门、最高工资
看到部门,肯定就是聚合。聚合的结果只会有最高工资这个数字,姓名,工资都无法聚合。但是我们可以通过聚合得到的表(此表包含部门,最高工资)与emp表进行笛卡尔积,再筛选不对的信息,最后就又变成了单表查询。
先看看部门的最高工资:
代码语言:javascript复制select deptno, max(sal) max_sal from emp group by deptno;
得到的这张表与emp表进行笛卡尔积,筛选出对应的数据
代码语言:javascript复制select ename, sal, tmp.deptno, max_sal from emp, (select deptno, max(sal) max_sal from emp group by deptno) tmp where emp.deptno=tmp.deptno;
最后,再补充条件,emp.sal = tmp.max_sal
select ename, sal, tmp.deptno, max_sal from emp, (select deptno, max(sal) max_sal from emp group by deptno) tmp where emp.deptno=tmp.deptno and emp.sal = tmp.max_sal;
就得到了最终满足条件的结果。
- 显示每个部门的信息(部门名,编号,地址)和人员数量
- 首先,人员数量一定是聚合出来的。
- 部门编号,地址,是在dept表中的
方法一:使用多表
将分组聚合得到的人员数量、部门表与dept表进行笛卡尔积,然后筛选对应信息,最后将得到的这个表再进行查询,得到我们最终需要的信息。
代码语言:javascript复制select t1.dname, t1.loc, t2.dept_num, t1.deptno from dept t1, (select deptno, count(*) dept_num from emp group by deptno) t2 where t1.deptno=t2.deptno;
方法二:子查询
代码语言:javascript复制-- 1. 对EMP表进行人员统计
select count(*), deptno from EMP group by deptno;
-- 2. 将上面的表看作临时表
select DEPT.deptno, dname, mycnt, loc from DEPT,
(select count(*) mycnt, deptno from EMP group by deptno) tmp
where DEPT.deptno=tmp.deptno;
实际上这与上面的形式一样,也是多表的范畴,因为MySQL下一切皆表,这种方式反而太过直接。
五.多表问题的指导思想
最后,通过我们上述的思考过程总结出 解决多表问题的本质:想办法将多表转化成单表,所以mysql中,所有select的问题全部都可以转化成单表问题!
六.合并查询
在实际应用中,为了合并多个select的执行结果,可以使用集合操作符 union,union all。
1. union
该操作符用于取得两个结果集的并集。当使用该操作符时,会自动去掉结果集中的重复行。
案例:将工资大于2500或职位是MANAGER的人找出来
2. union all
该操作符用于取得两个结果集的并集。当使用该操作符时,不会去掉结果集中的重复行。
案例:将工资大于2500或职位是MANAGER的人找出来
union以及union all需要注意的是在连接时,两者显示的字段类型以及数量、位置必须完全一致,否则无法合并。