【MySQL】SQL里的“套娃”与“拼图”:子查询和合并查询

文章目录

1. 子查询(嵌套查询)

子查询是指嵌入在其他sql语句中的select语句,也叫嵌套查询。我们前面的文件已经做过简单的了解了,这个文章我们正式来学习它。(案例依然使用前面文章的三张表)

1.1 单行子查询

即返回一行记录的子查询

案例:显示与SMITH同一部门的所有员工的信息

那这里的筛选条件应该怎么写?

那就应该是要筛选记录的部门编号和SMITH的部门编号相等

select * from emp where deptno=(select deptno from emp where ename='SMITH');

其中select deptno from emp where ename='SMITH'即为子查询语句,它只返回一行结果(且单列),故称为单行子查询。

1.2 多行子查询

返回多行记录的子查询

1.2.1 in 关键字

案例:查询和10号部门的工作岗位相同的雇员的名字,岗位,工资,部门号,但是不包含10号部门自己的

可能不能很好理解,我们来分析一下:


首先要知道10号部门的工作岗位是什么,然后要找的是工作岗位与这个(10号部门的工作岗位)相同的雇员的信息,包括姓名、岗位、工资、部门号,但是要求雇员不能是10号部门中的。
那首先我们来看一下10号部门中都有哪些工作岗位
select job from emp where deptno=10;
我们看到有这样三种
那我们要找的员工信息就是这三种岗位的员工信息,具体为姓名、岗位、工资、部门号
那是不是这样写
select ename,job,sal,deptno from emp where job =(select job from emp where deptno=10);

但是我们发现这里报错了,说子查询返回超过一行。
没有问题,这里的子查询语句select job from emp where deptno=10;返回了多行结果(单列),这也是为什么叫做多行子查询 的原因。
那这里报错的原因是什么呢?
错误原因:子查询返回多行,但外层查询使用 = 进行比较,= 只能与单值比较。当子查询返回多行时,MySQL 无法判断应该用哪个值进行比较,因此报错。
所以这里就不能用=判断了,那应该用什么呢?
我们之前学过一个关键字in

这种场景应该使用它
select ename,job,sal,deptno from emp where job in (select job from emp where deptno=10);

此时就找到这三种岗位的所有员工相关信息了,但是题目还要求不包含10号部门自己的。
也就是这里的员工,如果是属于10号部门,那么我们应该过滤掉他的信息
select ename,job,sal,deptno from emp where job in (select job from emp where deptno=10) and deptno !=10;

这样得到的结果就是正确的了

拓展:多表多行子查询

然后我们做一下拓展,我来加一个条件,要求再加上显示员工的部门名称。那要怎么做呢?

再来回顾一下表结构

上面的信息是在emp表中的(当然也在上面的结果表中),而部门名称是在dept表中的。所以就要进行多表查询,那这是我们上篇文章学的内容
那首先我们可以看一下两张表的笛卡尔积,然后从中筛选

现在得到的这张表中包含了原题目要求的信息,同时包含了我们刚才附加的员工的部门名称
那筛选条件应该是什么
我们说过笛卡尔积中通常是用很多无意义的数据的

比如SMITH这个人,他的部门号已经确定了就是20,那在笛卡尔积中他与dept表中部门号非20的记录组合得到的记录是无意义的,我们就要过滤掉
那就应该是让第一张表中的部门号=第二张表中的部门号
select * from (select ename,job,sal,deptno from emp where job in (select job from emp where deptno=10) and deptno !=10) tmp,dept where tmp.deptno=dept.deptno;

然后我们最终需要的信息就是雇员的名字,岗位,工资,部门号,再加上一个部门名称

1.2.2 all 关键字

案例:显示工资比部门30的所有员工的工资高的员工的姓名、工资和部门号

我们还是先来分析一下:

要显示比"这个工资"高的所有员工的姓名、工资和部门号;这个工资指的是什么呢?
是30号部门的员工中的最高工资,因为要比30号部门的所有员工工资高嘛,那比部门中最高工资高,自然就比所有员工工资高了。
所以,总结一下就是要显示工资比30号部门的最高工资还高的员工的姓名、工资和部门号
那我们先来拿到30号部门的最高工资
那这很简单,使用聚合统计最大值即可

那然后要显示所有工资比这个搞得员工信息,那就可以将他作为子查询语句

然后题目要的信息只有姓名、工资和部门号

解法2(使用all关键字)

除了上面的方法,我们还可以使用另外一种解法:

我先筛选出30号部门中所有人的工资

我们看到有多个数据,现在要查找的员工信息是工资比这里面所有人工资都高的员工。
上面我们使用max统计了最大值,这当然没问题。
那除此之外还可以这样,使用all关键字
select ename,sal,deptno from emp where sal>all(select sal from emp where deptno=30);

select sal from emp where deptno=30返回了多行数据(多行子查询)
如果直接跟它比较大小就会报错

因为不知道到底要和哪一行比,加上all关键字就表明,要大于(>)这里面的所有数据
大家可以对比一下两种方法的结果是一样的。

1.2.3 any关键字

案例:显示工资比部门30的任意一个员工的工资高的员工的姓名、工资和部门号

还是先来分析一下:

上一道题目的要求是找出工资比30号部门的所有 员工的工资都高的员工的姓名、工资和部门号(所以就是比最大值高)。
而这道题是任意,显示工资比部门30的任意员工的工资高的员工的姓名、工资和部门号,那这里就应该是比最小值高就行了。(比任意一个员工的工资高就行,那我比工资最低的那个员工高当然也符合)

那我们来写一下:

首先还是显示出30号部门的员工工资来看一下。

当然有重复的数据其实可以去一下重

那按照上面的分析,其实和上一题类似,我们可以使用聚合函数
比最低工资高就行了

同样的,也有第二种方法,我们可以使用any关键字
select * from emp where sal>any (select distinct sal from emp where deptno=30);
子查询语句select distinct sal from emp where deptno=30返回了多行,我只要比其中一个高就行了
当然最后只要名字、工资、部门号

1.3 多列子查询

上面讲的,不论是单行子查询还是多行子查询,大家会看上面的例子会发现子查询返回的结果都是单列的。

那下面就来学习一下多列子查询,多列子查询即子查询语句返回多列

下面来看案例

案例:查询和员工SMITH的部门和岗位完全相同的所有雇员,不含SMITH本人

分析一下:

先拿出SIMTH的部门和岗位看一下

那然后我们就可以使用它作为子查询条件进行筛选(这里返回了两列,故称为多列子查询)
那where子句中就应该是两列与两列的比较
怎么做呢?
select * from emp where(deptno,job)=(select deptno,job from emp where ename='SMITH');

(如果子查询返回的是多行多列那就把=换成in即可)
但是还要求不包括SMITH本人,那多加一个条件就行了

1.4 在from子句中使用子查询

我们上面学习的子查询语句都是出现在where子句中的,作为判断条件的一部分。

不过呢

子查询还可以出现在from子句中。这里要用到数据查询的一个技巧,即把子查询的结果当做一个临时表使用

来看案例:

1.4.1 显示所有高于自己部门平均工资的员工的姓名、部门、工资、平均工资

大家可以先自己想一想,其实不太好搞

分析一下:

要显示所有高于自己 部门平均工资的员工的姓名、部门、工资、平均工资
那可以先考虑显示出每个部门的平均工资
select deptno,avg(sal) from emp group by deptno;

那再往下我们该怎么做呢?
🆗,如果我们现在把上面这个各部门平均工资的结果看作一张表,跟emp表进行一个笛卡尔积。
那么笛卡尔积的结果中就包含了每个员工的信息以及这个员工所有部门的平均工资。
然后就可以在这一张表中进行比较筛选了,那么先来看一下这两者笛卡尔积的结果
select * from emp,(select deptno,avg(sal) from emp group by deptno) tmp;

那同样的,我们说过很多遍了,笛卡尔积中通常是有很多的无意义数据的,比如SMITH这个人,他所在的部门是确定的,那他与tmp表中其它部门的信息组合是无意义的

所以来进行一个过滤

然后我们按照题目的要求进行最终的筛选即可,筛选条件就是自己的工资高于自己所在部门的平均工资

但是这样写是不行的,之前文章讲过where子句中不能使用聚合函数。所以可以给avg(sal)这一列进行一个重命名
select * from emp,(select deptno,avg(sal) as 平均工资 from emp group by deptno) tmp where emp.deptno=tmp.deptno and sal>平均工资;

就可以了。
最终,题目只要姓名、部门、工资、平均工资

那下面再来增加一个要求:额外显示员工的办公地点

拓展

那又该怎么做?loc在另一张表dept中

那是不是可以把上面的最终结果作为一张表,再和dept这张表进行笛卡尔积,然后从中筛选
先看两者的笛卡尔积
select* from dept,(select ename,emp.deptno,sal,平均工资 from emp,(select deptno,avg(sal) as 平均工资 from emp group by deptno) tmp where emp.deptno=tmp.deptno and sal>al>平均工资) t2;

然后过滤掉无意义的记录
select* from dept,(select ename,emp.deptno,sal,平均工资 from emp,(select deptno,avg(sal) as 平均工资 from emp group by deptno) tmp where emp.deptno=tmp.deptno and sal>平均工资) t2 where dept.deptno=t2.deptno;

然后显示的列就是上题的姓名、部门、工资、平均工资再加上部门地点loc

1.4.2 查找每个部门工资最高的人的姓名、工资、部门、最高工资

分析一下:

select ename,sal,deptno,max(sal) from emp group by deptno
这样可以吗?
肯定是不行的,复习一下,之前的文章里讲过:使用group by子句的场景下,select后面跟的列,要么在GROUP BY子句中出现,要么被聚合函数处理
那我们还是先来看一下每个部门的最高工资

那emp表中是没有最高工资的,但是上面结果的这个临时表中有,那我们就可以考虑这两张表做笛卡尔积,然后结果中不就直接包含我们想要的所有信息了嘛
select * from emp,(select deptno,max(sal) from emp group by deptno) as tmp;

然后,过滤无意义的记录

但是题目要的是每个部门工资最高的员工信息
所以:

最后,我们只要姓名、工资、部门、最高工资
select ename,sal,emp.deptno,最高工资 from emp,(select deptno,max(sal) 最高工资 from emp group by deptno) as tmp where emp.deptno=tmp.deptno and sal=最高工资;

1.4.3 显示每个部门的部门名,编号,地址和员工数量

分析:

员工数量哪一张表中都没有,所以肯定是需要我们聚合统计的
先来统计显示一下

然后其余的信息部门名称,编号和地址不就都在dept表中嘛
所以就可以考虑将这两张表进行一个笛卡尔积

然后,过滤掉无意义的数据

最后,筛选我们想要的列即可
select dname,dept.deptno,loc,部门人数 from dept,(select deptno,count(ename) 部门人数 from emp group by deptno) tmp where dept.deptno=tmp.deptno;

2. 合并查询

在实际应用中,为了合并多个select的执行结果,可以使用集合操作符 union,union all

2.1 union

该操作符用于取得两个结果集的并集(什么是并集相信咱们理工科的人都应该是比较清楚的,就不解释了)。当使用该操作符时,会自动去掉结果集中的重复行。

案例:将工资大于2500或职位是MANAGER的人找出来

这个其实我们where子句里面加个or很简单就搞定了

select ename,sal,job from emp where sal>2500 or job='MANAGER';

另一种方法就是使用我们这里提到的union

用于取得两个结果集的并集。
那上面select ename,sal,job from emp where sal>2500 or job='MANAGER';这条语句其实就是
select ename,sal,job from emp where sal>2500;
select ename,sal,job from emp where job='MANAGER';
这两条SQL语句结果的并集嘛

所以使用union将这两条sql合并起来
select ename,sal,job from emp where sal>2500 union select ename,sal,job from emp where job='MANAGER';

结果和上面的方法是一样的

2.2 union all

非常简单:

union all与union的区别就是不会对合并的结果进行去重
select ename,sal,job from emp where sal>2500 union all select ename,sal,job from emp where job='MANAGER';

相关推荐
知识分享小能手1 小时前
SQL Server 2019入门学习教程,从入门到精通,SQL Server 2019 开发企业人事管理系统 — 语法知识点及使用方法详解(21)
sql·学习·sqlserver
秋氘渔2 小时前
MySQL EXPLAIN命令详解:SQL查询性能分析与优化指南(基础篇)
sql·mysql·adb
禹凕2 小时前
MYSQL——基础知识(MYSQL 索引)
数据库·mysql
Zhu_S W2 小时前
MySQL大表优化完全指南
数据库·mysql
Hx_Ma162 小时前
mybatis练习2
java·数据库·mybatis
CN-David2 小时前
CentOS搭建Mycat中间件
linux·mysql·中间件·centos·mariadb
星辰_mya2 小时前
Kafka Producer 发送慢 → TPS 骤降 90%
java·数据库·kafka
花间相见2 小时前
【Ubuntu实用工具】—— Fcitx5 输入法安装与完整配置指南(新手友好+避坑版)
linux·数据库·ubuntu
kyle~2 小时前
MySQL基础知识点与常用SQL语句整理
android·sql·mysql