【MySQL】表的复合查询
表的复合查询
- 一. 单表查询
- 二. 多表查询
- 三. 自连接
- 四. 子查询
- 1. 单行子查询
- 2. 多行子查询
- 3. 多列子查询
- 4. 在 from 子句中使用子查询
- 5. 合并查询
- 1. union
- 2. union all
- 五. SQL 实战
一. 单表查询
导入 scott_data.sql 文件,在 第三部分的分组查询中
# 使用 scott 数据库
use scott;# 查看三张表
select * from dept;
select * from emp;
select * from salgrade;
查询工资高于500或岗位为MANAGER的员工,同时还要满足他们的姓名首字母为大写的J
在where子句中指明筛选条件为工资高于500或岗位为MANAGER,并且通过模糊匹配指明员工姓名的首字母为大写的J,在select的column列表中指明要查询的列为姓名、工资和岗位。
select ename, sal, job from emp where (sal > 500 or job = 'MANAGER') and ename like 'J%';select ename, sal, job from emp where (sal > 500 or job = 'MANAGER') and substring(ename, 1, 1) = 'J';
按照部门号升序而雇员的工资降序排序
在select的column列表中指明要查询的列为姓名、部门号和工资,在order by子句中依次指明按部门号排升序和按员工工资排降序,即不同部门的员工按照部门号排升序,而同一部门的员工按员工工资排降序。
select ename, deptno, sal from emp order by deptno asc, sal desc;
使用年薪进行降序排序
在select的column列表中指明要查询的列为姓名和年薪,在order by子句中指明按年薪进行降序排序。
select ename 姓名, sal 月薪, comm 奖金, sal * 12 + ifnull(comm, 0) 年薪 from emp order by 年薪 desc;
- 由于NULL与任何值做计算得到的结果都是NULL,因此在计算年薪时不能直接用月薪的12倍加上每个员工的奖金,这样可能导致得到的年薪为NULL值。
- 在计算每个员工的年薪时,应该通过ifnull函数判断员工的奖金是否为NULL,如果不为NULL则ifnull函数返回员工的奖金,如果为NULL则ifnull函数返回0,避免让NULL值参与计算。
显示工资最高的员工的名字和工作岗位
- 解决该问题需要进行两次查询,先对员工表进行一次查询得到最高工资,然后再根据最高工资对员工表进行一次查询,得到工资等于最高工资的员工的姓名和岗位。
- 此外,这种问题还可以使用子查询,将第一次查询的SQL语句用括号括起来,作为最高工资直接在第二次查询的SQL语句中使用。
select max(sal) from emp;
select ename, job, sal from emp where sal = 5000;# 两条sql语句合成一条sql语句
select ename, job, sal from emp where sal = (select max(sal) from emp);
显示工资高于平均工资的员工信息
解决该问题也需要进行两次查询,先对员工表进行一次查询得到平均工资,然后再根据平均工资对员工表进行一次查询,筛选出工资高于平均工资的员工信息,该问题同样可以使用子查询。
select ename, job, sal from emp where sal > (select avg(sal) from emp);
显示每个部门的平均工资和最高工资
在group by子句中指明按照部门号进行分组,在select语句中使用avg函数和max函数,分别查询每个部门的平均工资和最高工资。
select deptno, format(avg(sal), 2) 平均工资, max(sal) 最高工资 from emp group by deptno;
显示平均工资低于2000的部门号和它的平均工资
在group by子句中指明按照部门号进行分组,在select语句中使用avg函数查询每个部门的平均工资,在having子句中指明筛选条件为平均工资小于2000。
select deptno, avg(sal) 平均工资 from emp group by deptno having 平均工资 < 2000;
显示每种岗位的雇员总数,平均工资
在group by子句中指明按照岗位进行分组,在select语句中使用count函数和avg函数,分别查询每种岗位的雇员总数和平均工资。
select job, count(*) 人数, format(avg(sal), 2) 平均工资 from emp group by job;
二. 多表查询
- 上面的基础查询都是在一张表的基础上进行的查询,而实际开发中往往需要将多张表关联起来进行查询,这就叫做多表查询。
- 在进行多表查询时,只需要将多张表的表名依次放到from子句之后,用逗号隔开即可,这时MySQL将会对给定的这多张表取笛卡尔积,作为多表查询的初始数据源。
- 多表查询的本质,就是对给定的多张表取笛卡尔积,然后在笛卡尔积中进行查询。
所谓的对多张表取笛卡尔积,就是得到这多张表的记录的所有可能有序对组成的集合,比如下面对 emp 表和 dept 表进行多表查询,由于查询语句中没有指明筛选条件,因此最终得到的结果便是员工表和部门表的笛卡尔积。
select * from emp, dept;
- 员工表和部门表的笛卡尔积由两部分组成,前半部分是员工表的列信息,后半部分是部门表的列信息。
- 对员工表和部门表取笛卡尔积时,会先从员工表中选出一条记录与部门表中的所有记录进行组合,然后再从员工表中选出一条记录与部门表中的所有记录进行组合,以此类推,最终得到的就是这两张表的笛卡尔积。
笛卡尔积的初步过滤
需要注意的是,对多张表取笛卡尔积后得到的数据并不都是有意义的,比如对员工表和部门表取笛卡尔积时,员工表中的每一个员工信息都会和部门表中的每一个部门信息进行组合,而实际一个员工只有和自己所在的部门信息进行组合才是有意义的,因此需要从笛卡尔积中筛选出员工的部门号和部门的编号相等记录。
select * from emp, dept where emp.deptno = dept.deptno;
说明: 进行笛卡尔积的多张表中可能会存在相同的列名,这时在选中列名时需要通过表名.列名的方式进行指明。
显示部门号为10的部门名,员工名和工资
由于部门名只有部门表中才有,而员工名和员工工资只有员工表中才有,因此需要同时使用员工表和部门表进行多表查询,在where子句中指明筛选条件为员工的部门号等于部门编号,并且部门号为10的记录。
select emp.deptno, dname, ename, sal from emp, dept
where emp.deptno = dept.deptno and emp.deptno = 10;
- 第一个筛选条件已经筛选出员工的部门号和部门编号相等的记录,因此在筛选部门号等于10的部门时,可以使用员工表中的部门号,也可以使用部门表中的部门编号。
显示各个员工的姓名,工资,及工资级别
由于员工名和工资只有员工表中才有,而工资级别只有工资等级表中才有,因此需要同时使用员工表和工资等级表进行多表查询,在where子句中指明筛选条件为员工的工资在losal和hisal之间的记录。
select ename, sal, grade, losal, hisal from emp, salgrade
where sal between losal and hisal;
- 员工表和工资等级表的笛卡尔积中,将每一个员工的信息和每一个工资等级的信息都进行了组合,而实际一个员工只有和自己的工资对应的工资等级信息进行组合才是有意义的。
- 因此需要根据各个工资等级的最低工资和最高工资判断一个员工是否属于该工资等级,进而筛选出有意义的记录。
三. 自连接
自连接:在同一张表连接查询(同一张表做笛卡尔积,但是需要重命名)
- 自连接是指在同一张表进行连接查询,也就是说我们不仅可以取不同表的笛卡尔积,也可以对同一张表取笛卡尔积(同一张表做笛卡尔积,但是需要重命名)
- 如果一张表中的某个字段能够将表中的多条记录关联起来,那么就可以通过自连接将表中通过该字段关联的记录组合起来。
select * from salgrade as t1, salgrade as t2;
显示员工FORD的上级领导的编号和姓名(mgr是员工领导的编号–empno)
两次单表查询:先对员工表进行查询得到FORD的领导的编号,再根据领导的编号对员工表进行查询得到FORD领导的编号和姓名。
select mgr from emp where ename = 'FORD';
select empno, ename from emp where empno = 7566;
子查询:先对员工表进行查询得到FORD的领导的编号,再根据领导的编号对员工表进行查询得到FORD领导的编号和姓名。
select empno, ename from emp where empno = (select mgr from emp where ename = 'FORD');
自连接:因为员工表中的mgr字段能够将表中员工的信息和员工领导的信息关联起来。
对员工表进行自连接后,在where子句中指明筛选条件为员工的领导编号等于领导的编号,这时就能筛选出每个员工信息与其领导信息组合形成的记录,进一步指明筛选条件为员工的姓名为FORD,这时便能筛选出员工FORD的信息和他的领导的信息组成的记录。
select e2.empno, e2.ename from emp e1, emp e2
where e1.ename = 'FORD' and e1.mgr = e2.empno;
- 由于自连接是对同一张表取笛卡尔积,因此在自连接时至少需要给一张表取别名,否则无法区分这两张表中的列。
四. 子查询
- 子查询是指嵌入在其他SQL语句中的查询语句,也叫嵌套查询。
- 子查询可分为单行子查询、多行子查询、多列子查询,以及在from子句中使用的子查询。
1. 单行子查询
显示与SMITH同一部门的员工
两次查询:先查询的部门编号,再通过单行部门编号进行查询。
select deptno from emp where ename = 'SMITH';
select * from emp where deptno = 20;
单行子查询:在子查询中查询SMITH所在的部门号,在where子句中指明筛选条件为员工部门号等于子查询返回的部门号,并且员工的姓名不为SMITH
select * from emp where deptno = (select deptno from emp where ename = 'SMITH');
自连接:因为和SMITH同一部门的员工的信息也在员工表当中,因此对员工表进行自连接后,在where子句中指明表1的员工姓名为SMITH,并且表1和表2的部门号必须相等,并且表2的员工姓名不为SMITH,这样也能筛选出和SMITH同一部门的员工信息。
select e2.* from emp e1, emp e2
where e1.deptno = e2.deptno and e1.ename = 'SMITH' and e2.ename != 'SMITH';
2. 多行子查询
in关键字
:查询和10号部门所有工作岗位相同的雇员的名字,岗位,工资,部门号,但是不包含10号部门。
两次查询:先求出要查询的工作岗位,再通过多行工作岗位进行查询。
select distinct job from emp where deptno = 10;select ename, job, sal, deptno from emp
where job in ('MANAGER', 'PRESIDENT', 'CLERK') and deptno <> 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 > (select max(sal) from emp where deptno = 30);
多行子查询
select ename, sal, deptno from emp
where sal > all(select distinct sal from emp where deptno = 30);
any关键字
:显示工资比部门30的任意员工的工资高的员工的姓名、工资和部门号。
单行子查询
select ename, sal, deptno from emp
where sal > (select min(sal) from emp where deptno = 30);
多行子查询
select ename, sal, deptno from emp
where sal > any(select distinct sal from emp where deptno = 30);
3. 多列子查询
- 单行子查询是指子查询返回单列,单行数据。
- 多行子查询是指子查询返回单列,多行数据。
- 无论是单行子查询,还是多行子查询,都是针对单列而言的。
- 多列子查询则是指查询返回多个列数据的子查询语句。
查询和SMITH的部门和岗位完全相同的所有雇员,不含SMITH本人
单行多列子查询:
select ename, deptno, job from emp
where (deptno, job) = (select deptno, job from emp where ename = 'SMITH') and ename != 'SMITH';
多行多列子查询
select ename, deptno, job from emp
where (deptno, job) in (select deptno, job from emp where ename = 'SMITH') and ename != 'SMITH';
- 多列子查询得到的结果是多列数据,在比较多列数据时需要将待比较的多个列用圆括号括起来。
- 多列子查询返回的如果是多行数据,在筛选数据时也可以使用in、all和any关键字。
- 目前的所有子查询都在where子句中,充当判断条件。
- 任何时刻,查询出来的临时结构,本质在逻辑上都是表结构。
4. 在 from 子句中使用子查询
- 子查询语句在 where 子句中,充当判断条件。
- 子查询语句在 from 子句中时,将子查询的结果当做一个临时表使用。
显示每个高于自己部门平均工资的员工的姓名、部门、工资、平均工资
先利用group by求出每一个部门的平均工资,再将上述结果当做一张表,与emp表进行多表查询。
select deptno, avg(sal) avg_sal from emp group by deptno;select ename, emp.deptno, sal, avg_sal
from emp, (select deptno, avg(sal) avg_sal from emp group by deptno) tmp
where emp.deptno = tmp.deptno and emp.sal > tmp.avg_sal;
说明: 在from子句中使用子查询时,必须给子查询得到的临时表取一个别名,否则查询将会出错。
#进阶:在此基础上,显示部门编号deptno所在的办公地点loc(位于表dept中)
select t1.ename, t1.deptno, dept.loc, t1.sal, t1.avg_sal
from dept, (select ename, emp.deptno, sal, avg_sal from emp, (select deptno, avg(sal) avg_sal from emp group by deptno) tmp
where emp.deptno = tmp.deptno and emp.sal > tmp.avg_sal) t1 where dept.deptno = t1.deptno;
查找每个部门工资最高的人的姓名、工资、部门、最高工资
先利用group by求出每一个部门的最高工资,再将上述结果当做一张表,与emp表进行多表查询。
select deptno, max(sal) max_sal from emp group by deptno;select ename, sal, t1.deptno, max_sal
from emp t1, (select deptno, max(sal) max_sal from emp group by deptno) t2
where t1.deptno = t2.deptno and t1.sal = t2.max_sal;
显示每个部门的信息(部门名、编号、地址)和人员数量
子查询 + 多表查询:先利用group by求出每一个部门的人数,再将上述结果当做一张表,与dept表进行多表查询。
select deptno, count(*) people_count from emp group by deptno;select dname, t1.deptno, loc, people_count
from dept t1, (select deptno, count(*) people_count from emp group by deptno) t2
where t1.deptno = t2.deptno;
多表查询:先对员工表和部门表取笛卡尔积,在where子句中指明筛选条件为员工的部门号等于部门的编号,筛选出有意义的记录,在group by子句中指明按照部门号进行分组,分别统计出每个部门的人数。
select dname, dept.deptno, loc, count(*) people_count
from emp, dept
where emp.deptno = dept.deptno
group by dname, dept.deptno, loc;
- 因为在select语句中新增了要显示部门名和所在地址,因此需要在group by子句中也添加这两个字段,表明当部门号相同时按照部门名进行分组,当部门名也相同时继续按照所在地址进行分组。
- 但实际在上述场景中部门号相同的记录,它们的部门名和所在地址也一定是相同的,因此在我们看来group by中继续添加这两个字段没什么意义,但MySQL语句要求我们必须添加。
- 解决多表问题的本质:将多表转化为单表,进行单表查询!
5. 合并查询
在实际应用中,为了合并多个 select 的执行结果,可以使用集合操作符 union,union all
1. union
union:用于取得两个结果集的并集,当使用该操作符时,会自动去掉结果集中的重复行。
将工资大于2500或职位是MANAGER的人找出来
select ename, job, sal from emp where sal > 2500 or job = 'MANAGER';
select ename, job, sal from emp where sal > 2500
union select ename, job, sal from emp where job = 'MANAGER';
2. union all
union all:用于取得两个结果集的并集,当使用该操作符时,不会去掉结果集中的重复行。
将工资大于2500或职位是MANAGER的人找出来
select ename, job, sal from emp where sal > 2500
union all select ename, job, sal from emp where job = 'MANAGER';
- 待合并的两个查询结果的列的数量必须一致,否则无法合并。
- 待合并的两个查询结果对应的列属性可以不一样,但不建议这样做。
五. SQL 实战
- 牛客:获取所有员工当前的manager
- 牛客:获取所有非manager的员工emp_no