复合查询+表的内外连接
点赞👍👍收藏🌟🌟关注💖💖
你的支持是对我最大的鼓励,我们一起努力吧!😃😃
前面我们讲解的mysql表的查询都是对一张表进行查询,在实际开发中这远远不够,未来可能还有多表查询,子查询。
1.基本查询回顾
查询工资高于500或岗位为MANAGER的雇员,同时还要满足他们的姓名首字母为大写的J
下面有两种写法,都没问题
select * from emp where (sal>500 or job='MANAGER') and ename like 'J%'; select * from emp where (sal>500 or job='MANAGER') and 'J'=substring(ename,1,1);
按照部门号升序而雇员的工资降序排序
select * from emp order by deptno asc,sal desc;
使用年薪进行降序排序
我们看到这个表奖金这一列有的是有的,有的是没有的为null,年薪=月薪*12+奖金
,但是null不参与运算,我们前面刚学的ifnull函数这个时候就可以用上了
ifnull(val1,val2),如果val1为null,返回val2,否则返回val1的值
select ename,sal*12+ifnull(comm,0) 年薪 from emp order by 年薪 desc;
显示工资最高的员工的名字和工作岗位
最高工资我们可以使用聚合函数做统计,但是聚合函数只是对一列的相同数据做聚合,今天还要有名字,所以单纯的做聚合是无法满足需求的,它只能最高工资是多少。名字和工作岗位显示不出来。
因此我们先找最高工资是多少。然后拿着这个最高工资去把这个人和工作岗位找到。很显然这是两句select。
但是这样写太挫了。怎么办呢,想办法把两个select查询语句合在一块写。
sql允许在一条sql内部在执行select查询,这称为子查询,先执行内部的sql然后在执行外部的sql
select ename,job,sal from emp where sal=(select max(sal) from emp);
显示工资高于平均工资的员工信息
还是要先使用聚合函数把平均工资找到,然后在找到工资高于平均工资的员工。因此还可以使用子查询。
select * from emp where sal>(select avg(sal) from emp);
显示每个部门的平均工资和最高工资
首先是对部门分组,分组就是分表,表分好之后一个表内部门号是相同的,然后就是在每张表中找每个部门的平均工资和最高工资。其实在子表和在一个整表内找是一样的。在我看来不管是整表还是子表其实都是在表内查询。
select deptno,max(sal) 最高,avg(sal) 平均 from emp group by deptno;
显示平均工资低于2000的部门号和它的平均工资
这里要的是平均工资低于2000的部门号,因此要按照部门号分组,统计出每个部分的平均工资,然后在筛选出低于2000的部门号和平均工资
select deptno,avg(sal) myavg from emp group by deptno having myavg<2000;
显示每种岗位的雇员总数,平均工资
这个不就是对岗位分组,然后在统计每个岗位的人数和平均工资
select job,count(*),avg(sal) from emp group by job;
2.多表查询
以前我们都是从一张表拿数据,但是实际开发中往往数据来自不同的表,所以需要多表查询。
显示雇员名、雇员工资以及所在部门的名字
我们发现上面emp表中是没有部门名称的,换句话说要的数据是从两张表来的。
员工名和员工工资来自于emp表,部门名称来来自于dept表,因此注定了我们要将两个表做整合然后在查询。
select * from emp,dept;
可以看到形成了一张大表,仔细观察一下,将两张表信息做整合的时候,就光SMITH这一条消息就和整个dept表做组合形成了更多的记录,发现下面都是这样的。
我们可以看到如果今天对两张表的信息做组合,新形成表本质是将两张表中数据进行穷举组合的结果。我们把它称之为笛卡尔积。
在我们看来这不就是把两张表变成了一张表吗。所以未来在做数据的查找的时候,不就还是相当于单表的查找吗!然后就可以按照条件筛选出想要的信息。注意穷举是把所有组合结果都放在一起了,但是有些信息是有无意义的,因此可以先去除无意义的信息(不过还是看具体情况在决定是否保留),然后在按条件查找。
去除无效信息
在按条件筛选
在我们看来mysql一切皆表,换句话说这里做笛卡尔积之后,它形成的组合结果也是表结构,然后按照条件筛选
显示部门号为10的部门名,员工名和工资
select ename,sal,emp.deptno,dept.dname from emp,dept where emp.deptno=dept.deptno;
注意,如果指定的字段是这个表中是唯一的字段,不用指定表名直接显示即可,如果不是这个表中唯一的字段就需要指定表名显示,用来区分要显示是那个表的字段。
显示各个员工的姓名,工资,及工资级别
emp表中有员工的姓名和工资,工资级别在工资表里的,因此也还要将两个表笛卡尔积。
select ename,sal,grade from emp t1,salgrade t2 where t1.sal between t2.losal and t2.hisal;
上面我们是将两个不同的表做笛卡尔积,那可不可以把同一张表做笛卡尔积呢?
3.自连接
自连接是指同一张表做笛卡尔积。
我们发现直接把同一张表做笛卡尔积是不行的。主要原因这是同一张表这样不太好,字段名有重复不知道用的是那个表的字段名。
因此我们可以给两个表做重命名。
重命名也可以对表进行重命名,一旦对表进行重命名之后几乎可以在这条sql语句任何地方出现。因为sql语句执行一定是先告诉是从那个表拿数据。
我们看到同一个表也是拿着前面的表每一条记录去和后面的表中所有记录做组合。所以哪怕是同一张表也可以做笛卡尔积,只不过是对表名重新命名一下即可。
显示员工FORD的上级领导的编号和姓名(mgr是员工领导的编号–empno)
我们首先会想到把这个人的领导编号拿到,然后根据这个编号在去找这个人拿到他的信息。两条select查询语句,但是我们可以把两个整和成一条sql语句
select empno,ename from emp where empno=(select mgr from emp where ename='FORD');
还有一种做法,我们发现 先找到FORD的领导编是要从 EMP 表中找,然后根据领导编号找领导信息也是要从 EMP 表中找。 因此我们可以把同一张表自连接然后再找。
select t2.empno,t2.ename from emp t1,emp t2 where t1.mgr=t2.empno and t1.ename='FORD';
4.子查询
在之前编写的时候,子查询我们也写了一些。现在我们正式来说一下子查询的概念。除了刚才的笛卡尔积是一种整合表的做法,子查询也是多表查询或者一张表中复杂查询时常用的做法。
子查询是指嵌入在其他sql语句中的select语句,也叫嵌套查询
一般我们在子查询时依赖的永远都是子查询查出来的结果,根据结果我们可以把子查询划分为单列单行子查询、单例多行子查询、多列单行子查询、多列多行子查询。
4.1单列子查询
单例单行子查询
显示SMITH同一部门的员工
首先会执行子查询将SMITH的部门号查出来,然后充当外面select的筛选条件。
select * from emp where deptno=(select deptno from emp where ename='SMITH');
子查询筛选出来的是单列单行的信息。
单列多行子查询
子查询筛选出来的是单例多行的信息。
in关键字判断一个列值是否在集合中。
查询和10号部门的工作岗位相同的雇员的名字,岗位,工资,部门号,但是不包含10自己的
select ename,job,sal,deptno from emp where job in(select job from emp where deptno) and deptno<>10;
查询和10号部门的工作岗位相同的雇员的名字,岗位,工资,部门号,但是不包含10自己的,并且知道对应的员工属于哪一个部门的名字
还想要名字怎么办呢,但是emp里面并没有部门名字,只有dept表里面有。结合刚才所学,我们可以进行多表查询。首先确定一定要用的是dept表,还有一张表用谁呢?刚才我们不是已经得到一张表了。所以就把dept表和刚才的表做笛卡尔积。
子查询不仅可以在where充当筛选条件,还可以在from中,先用下面在解释。
然后去除不对的信息,就得到我们要的数据了
select ename,job,sal,tmp.deptno,dname from (select ename,job,sal,deptno from emp where job in(select job from emp where deptno=10) and deptno<>10) as tmp,dept where tmp.deptno=dept.deptno;
我们不仅仅用子查询把要的结果筛选出来,我想说的是,一个SQL整体的查询结果本身就是表结构,mysql一切解表,所以不要认为只有物理上真实存在的表才可以做笛卡尔积,我们可以将一个查出来的表结构也可以和其他表或者其他查询结果做笛卡尔积。其次,子查询不仅能出现在where后面充当判断条件,而且也能出现在from后面充当笛卡尔积。 在from哪里解释。
显示工资比部门30的所有员工的工资高的员工的姓名、工资和部门号
可能会想到先把30号部门最高工资先拿到,然后在去筛选比30号部门最高工资还高的信息。
下面这种写法我们也是先查找30部分最高工资然后充当筛选条件,在筛选
select ename,sal,deptno from emp where sal>(select max(sal) from emp where deptno=30);
除了这样的写法之外,我们还有一种做法,先把30号部门工资筛选出来,然后使用
all关键字表示全部
select ename,sal,deptno from emp where sal>all(select distinct sal from emp where deptno=30);
显示工资比部门30的任意员工的工资高的员工的姓名、工资和部门号(包含自己部门的员工)
只要比30号部分任意一个人工资高就可以了
any关键字表示任意
select ename,sal,deptno from emp where sal>any(select distinct sal from emp where deptno=30);
4.2多列子查询
单行子查询是指子查询只返回单列,单行数据;多行子查询是指返回单列多行数据,都是针对单列而言的,而多列子查询则是指查询返回多个列数据的子查询语句。
查询和SMITH的部门和岗位完全相同的所有雇员,不含SMITH本人
select * from emp where (deptno,job)=(select deptno,job from emp where ename='SMITH') and ename<>'SMITH';
子查询查出来的多列单行,没关系mysql支持( ,)
select * from emp where (deptno,job) in (select deptno,job from emp where ename='SMITH') and ename<>'SMITH';
我们发现这里也支持子查询多列多行
目前全部的子查询,全部都在where子句中,充当判断条件!但是任何时刻,查询出来的临时结构,本质在逻辑上也是表结构!
5.在from子句中使用子查询
子查询不仅可以出现where中充当判断条件,也可以出现在from中,from是在sql中告诉数据库去那个表里拿数据。在这里说一下任意查出来的表结构在我看来全都是表结构。子查询语句出现在from子句中,把一个子查询结果当做一个临时表使用,可以解决很多问题。
显示每个高于自己部门平均工资的员工的姓名、部门、工资、平均工资
首先我们要把每个部门的平均工资拎出来,如果找呢?那就需要分组然后聚合统计了。然后找出高于自己部门平均工资的姓名、部门、工资、平均工资。这个怎么找呢?如果你想的子查询找出自己部门的平均工资然后在做筛选找出这个部门中高于平均工资的,你会发现你做不下去的,因为今天这里并不是一个部门。首先我们先把每个部门平均工资拿出来走一步看一步。
select deptno,avg(sal) myavg from emp group by deptno;
按照之前这就是多列多行子查询,然后在子查询去where中筛选可能就比较麻烦了
可以看我们目前有什么,目前我们有部门号和部门平均工资,在我看来这就是一张表,然后我手里还有一张员工表,所以是不是可以把两张表做笛卡尔积,做完之后每个员工它所在那个部门已经所在部门的平均工资,在新的表中不就有了吗
子查询做表必须要给一个别名
select * from emp,(select deptno,avg(sal) myavg from emp group by deptno) tmp;
这个笛卡尔积将两张表的信息做了穷举,可是做完笛卡尔积有些信息是有意义的,有些一看就无意义的的,就如一个员工所属部门它目前并不需要和它不是同一个部门号的平均工资做对比,如果将来需要再说。因此可以过滤掉。
select * from emp,(select deptno,avg(sal) myavg from emp group by deptno) tmp where emp.deptno=tmp.deptno;
筛选之后,现在这不就是一张符合条件的单表查询了吗
然后在这张表里,在筛选出需要的信息就可以了
select ename,emp.deptno,sal,myavg from emp,(select deptno,avg(sal) myavg from emp group by deptno) tmp where emp.deptno=tmp.deptno and sal>myavg;
如果再加要求呢,要求这些人的办公地点在哪里?很简单我们可以把上面筛选出来的表结构充当一个表然后再和物理存在的dept表做笛卡尔积。
然后不又得到一张表了吗
然后只把有意义的消息筛选出来,
select * from dept,(select ename,emp.deptno,sal,myavg from emp,(select deptno,avg(sal) myavg from emp group by deptno) tmp where emp.deptno=tmp.deptno and sal>myavg) tmp where dept.deptno=tmp.deptno;
在把谁在哪里上班,部门号是多少消息拿出来不就好了吗
select tmp.ename,dept.loc,tmp.deptno from dept,(select ename,emp.deptno,sal,myavg from emp,(select deptno,avg(sal) myavg from emp group by deptno) tmp where emp.deptnmp.deptno=tmp.deptno and sal>myavg) tmp where dept.deptno=tmp.deptno;
只要你想做还可以在笛卡尔积。所以我们面对非常复杂的查询本质上都是在任务分解,复杂问题是由简单问题构成的。
查找每个部门工资最高的人的姓名、工资、部门、最高工资
首先也是要分组聚合统计找每个部门的最高工资,只不过只能统计到部门号和部门工资,这个人其他信息是没有办法在group by找到的。然后我们把这个临时表结构和emp做笛卡尔积。 最后在筛选出来部门号相同的,这个时候不有我们想要的信息的一张表了吗,然后在筛选自己想要的信息。
然后筛选出部门号相同的信息,最后找出自己要的数据就可以了
select ename,sal,emp.deptno,mymax from emp,(select deptno,max(sal) mymax from emp group by deptno) tmp where emp.deptno=tmp.deptno and sal=mymax;
记住mysql一切皆表,所谓的一切皆表就意味着可以把查询出来的临时结果在from后面也充当表。
显示每个部门的信息(部门名,编号,地址)和人员数量
首先人员数量一定是聚合出来的员工表里面是没有这个信息的。其次部门名、编号、地址是在dept表里面的。因此我们还可以像上面的做法一样解决。
select t1.deptno,dname,loc,mycnt from dept t1,(select deptno,count(*) mycnt from emp group by deptno) t2 where t1.deptno=t2.deptno;
其实还有一种方法就是单纯使用多表,不涉及子查询,不过这样太简单粗暴了,不够优雅!直接对emp表和dept表做笛卡尔积,然后筛选出部门和相同的一张表,然后对这张表做分组,分组之后在做聚合统计。
select t2.deptno,t2.dname,t2.loc,count(*) mycnt from emp t1,dept t2 where t1.deptno=t2.deptno group by t2.deptno,t2.dname,t2.loc;
要的信息除了部门号和人员数量,还有编号和地址,但是根据我们前面所学知识知道分组聚合统计只有跟在group by后面的列和聚合函数才可以聚合情况下出现,因此还要对编号和地址分组。
这种方法并不推荐,推荐使用子查询多表这种方法。
总结一下:mysql在我的心里是没有多表结构的,永远就是一张表。group by在我看来也是一张表,分组就是分表。只要解决一个问题其他都是解决。 多张表我可以在where中充当判断条件,在from中也做一个表然后和其他表做笛卡尔积。所以根本就没有多表问题。
解决多表问题的本质:想办法将多表转化成为单表,所以mysql中,所有select的问题全部都可以转成单表问题!这就是我们多表查询的指导思想!
6.合并查询
在实际应用中,为了合并多个select的执行结果,可以使用集合操作符 union,union all,合并并不是笛卡尔积,笛卡尔积是将两个表的信息穷举。合并就是单纯的合起来。
union
该操作符用于取得两个结果集的并集。当使用该操作符时,会自动去掉结果集中的重复行。
将工资大于2500或职位是MANAGER的人找出来
其实我们可以直接筛选出来的,但是我们用一下union
我们可以看到这两条sql查询出来的信息是有重复的。
这里我们可以使用union把两条sql合并起来并且去掉重复的
不想去重使用union all,就会把所有信息保留
注意合并时,两个表结构列必须是一样的才能把两个表合并起来
7.表的内连和外连
表的连接分为内连和外连
7.1内连接
内连接实际上就是利用where子句对两种表形成的笛卡儿积进行筛选,我们前面学习的查询都是内连接,也是在开发过程中使用的最多的连接查询。
除了上面学习的from 逗号 连接两个表然后用where筛选有效信息,还可以用下面inner join连接两个表,然后用on并且可以用and级联多个筛选条件 对笛卡尔积筛选出有效信息。之前学到的其实就是内连接的一种。
语法:
select 字段 from 表1 inner join 表2 on 连接条件 and 其他条件;
显示SMITH的名字和部门名称
这是之前的写法
select ename,emp.deptno,dname from emp,dept where emp.deptno=dept.deptno and ename='SMITH';
标准内连接写法
select ename,emp.deptno,dname from emp inner join dept on emp.deptno=dept.deptno where ename='SMITH';
两种写法都可以得到同样的数据,换句话说这种标准写法可以让我们的sql逻辑更清楚 ,哪一个部分是要形成笛卡尔积的,那一部分是进一步做条件筛选的。当然也直接用and连接,不过还是建议用where,逻辑更清楚。
7.2外连接
外连接分为左外连接和右外连接
7.2.1左外连接
如果多表查询,我们想让左侧的表完全显示不要过任何过滤筛选,如果和右侧的表配不上,让右侧的都为空也可以。必须保持左侧表的全貌。我们就叫做左外连接。
语法:
select 字段名 from 表名1 left join 表名2 on 连接条件
我们发现目前这两张表id是对不上的。
查询所有学生的成绩,如果这个学生没有成绩,也要将学生的个人信息显示出来
如果要用id做内连接的话,那只有1,2号学生符合条件,而我们要保留左侧表结构要完全显示,那内连接就不能满足条件了。因此我们使用左外连接
select * from stu left join exam on stu.id=exam.id;
左侧表完全保留,右侧表按条件拼接,条件满足直接拼上,条件不满足拼null
7.2.2右外连接
如果联合查询,要求右侧的表完全显示我们就说是右外连接。
其实只有左外连接也可以,把顺序换一下就可以,不过mysql这里也给我提供了右外连接。
语法:
select 字段 from 表名1 right join 表名2 on 连接条件;
对stu表和exam表联合查询,把所有的成绩都显示出来,即使这个成绩没有学生与它对应,也要显示出来
select * from stu right join exam on stu.id=exam.id;
右侧表完全保留,左侧表按条件拼接,条件满足直接拼上,条件不满足拼null
列出部门名称和这些部门的员工信息,同时列出没有员工的部门
首先是要将dept表和emp表做连接,要求的是必须把部门全部显示出来即使没有员工,这不是要求以dep表为主吗。
这里使用左外连接和右外连接都可以,不过就是换一下位置
select dept.dname,emp.* from dept left join emp on dept.deptno=emp.deptno order by dept.deptno asc;
select dept.dname,emp.* from emp right join dept on dept.deptno=emp.deptno order by dept.deptno asc;