目录
一、多表查询的概念
二、笛卡尔积
1. 笛卡尔积的概念
2. 笛卡尔积使用案例
2.1 显示雇员名、雇员工资以及所在部门的名字
2.2 显示部门号为10的部门名,员工名和工资
2.3 显示所有员工的姓名、工资和工资级别
3. 自连接
3.1 自连接的概念
3.2 自连接案例:显示员工FORD的上级领导的编号和姓名
三、子查询
1. 子查询的概念
2. 单行子查询
2.1 单行子查询的概念
2.2 显示和SMITH同一部门的员工
3. 多行子查询
3.1 单行子查询概念
3.2 in关键字
3.3 笛卡尔积与子查询的组合
3.4 all关键字
3.5 any关键字
3.6 关键字总结
4. 多列子查询
4.1 多行子查询的概念
4.2 查询和SMITH的部门和岗位完全相同的所有雇员,不包含SMITH本人
5. 在from子句中使用子查询
5.1 显示每个高于自己所处部门平均工资的部门员工的姓名、部门、工资与平均工资
5.2 查找每个部门工资最高的人的姓名、工资、部门、最高工资
5.3 显示每个部门的信息(部门名,编号,地址)和人员数量
四、合并查询
1. 案例:查询工资大于2500或职位是MANAGER的人
五、表的内外连接
1. 内连接
1.1 内连接的含义
1.2 案例:显示SMITH的名字和部门名称
2. 外连接
2.1 左外连接
2.2 右外连接
一、多表查询的概念
在以前的文章中写的基本都是单表查询,即只对一张表查询。但是在实际中如果只能对一张表进行查询是远远不够的,因为实际开发中的数据往往来自不同的表,所以需要多表查询。
在了解多表查询前,我们要先准备好如下几个表:
其实就是上一篇文章“表的增删查改”中用的同一组表。如果大家没有,可以直接到网上搜“oracle 9i经典测试表”,可以直接找文章下载使用。表的内容也就不再过多介绍了。后面的几乎所有案例,都会用这几张表做例子。
在这后面的所有内容,都是属于多表查询。
二、笛卡尔积
1. 笛卡尔积的概念
在以前的案例中,我们查询表中数据的时候,from后面都是只带了一张表的。
但实际上,from后面其实是可以用逗号分隔,带上多张表的。这些表,既可以是不同的表,也可以是相同的表。
如果大家仔细观察一下这些组合就会发现,在多个表组合形成的新表中,其实就是将不同的表中的数据进行穷举组合,然后形成一张新表。
这种将不同表的数据进行穷举组合的方法,就叫做“笛卡尔积”。在以前的文章中讲过“mysql中一切皆表”。因此,笛卡尔积之后形成的结果,我们其实也可以看成一张表。对笛卡尔积后的结果进行操作,其实也就是对另一张单独的表进行操作。
那这个笛卡尔积到底有什么用呢?在这里就举几个例子为大家展示。
2. 笛卡尔积使用案例
2.1 显示雇员名、雇员工资以及所在部门的名字
在这道题中,要求显示雇员名和雇员工资,这个好办,因为它们都在emp表内。但是所在部门的名字就不一样了,emp表内只有所在部门的编号,名字则在另一张dept表内。这就要求我们要从两张表中查询数据。
从上面的笛卡尔积中我们可知,from后面是可以跟多张表的,因此,我们先将这两张表的数据组合穷举出来:
注意,由于这两张表组合后太大了,所以这里并没有将组合全部截完,而只截了一部分。
当我们拿到了这两张表组合后的所有数据后,就可以开始筛选了。要知道,我们仅仅是需要知道雇员的信息,但是当这两张表组合后我们会发现,由于emp表中的部门编号来源于dept表,因此,一个雇员的部门编号在emp表和dept表内应该是一样的。所以,添加筛选条件:
注意,当使用多表查询的时候,如果在指令中需要使用表的列名,就需要按照“表名.列名”的方式使用,以用于区分不同表的列数据。使用方法就和C/C++中使用对象内的数据差不多。
当我们拿到上面的数据后,就可以保证上面的数据中每个雇员的信息都是一一对应的。因此,我们只需要在select后面加上要显示的数据即可:
2.2 显示部门号为10的部门名,员工名和工资
这道题其实和上面的题差不多,都是要从emp和dept两张表中获取数据,不同的是这次不再是获取全部数据,而是获取部门号为10的员工的数据。
对于这道题,我们采用逐步分解的方法。首先,由于它是要到两张表中找数据,所以我们首先将两张表组合起来:
然后,要找部门号为10的员工的信息,所以直接将这两个表中部门号相同且为10的员工筛选出来:
最后,由于是只显示部门号为10的部门名、员工名和工资,所以在select后面添上要显示的内容:
2.3 显示所有员工的姓名、工资和工资级别
这道题需要的数据也是存在两张表中的,分别是emp表和salgrade表内。salgrade表内的数据如下所示:
对于这道题,由于需要用到两张表的数据,同样的,先组合起来:
此时获得的就是每个员工对应的所有信息了。在这道题中要求显示工资级别, 因此使用条件运算符between and筛选出每个员工的工资级别和符合要求的数据。
最后,由于只要求显示姓名、工资和工资级别,所以添加显示内容:
3. 自连接
3.1 自连接的概念
自连接,其实也是基于笛卡尔积的。在上文中我们就实验过,在from后面是可以用不同的表组合的。但是,其实同一张表也是可以组合的。
如果用相同的表进行组合,就需要给这个表分别取不同的别名。这其实就是自连接。
因此,自连接其实就是指在同一张表连接查询。
那么,我们在什么时候会用到自连接呢?为了让大家更清楚的看到实际情况,我们就用一个案例来说明。
3.2 自连接案例:显示员工FORD的上级领导的编号和姓名
关于员工的所有信息,我们其实都存在了emp表内。在这张表中,empno是员工编号,mgr是员工的上级领导的编号。该公司的所有员工都在这张表中。
在这里介绍两个解决方法。
方法一:拆分题目,用两条sql语句完成
首先,由于是要找FORD员工的上级领导,因此,我们可以将FORD找出来:
找到FORD员工后,就可以根据它的mgr找到它的上级领导:
虽然这种方法可以完成题目要求,但是这种方法使用两条sql语句,那如果我们想只用一条sql语句完成呢?
方法二:使用自连接完成
由于这个题目的要求是拿着表中的一个数据再到表中找另一个数据,因此我们可以先将表组合,形成一张新的表:
由于我们是需要找FORD员工的信息,所以将FORD员工的信息筛选出来:
此时我们就获得了这个员工在表中的所有组合。由于是要找它的领导,因此,该领导的编号应该和该员工的领导编号相同,再次筛选:
此时就得到该领导和该员工的组合数据了。因此根据题目要求,显示领导编号和领导姓名即可:
此时,就成功获取到了FORD员工的领导编号和领导姓名。
三、子查询
1. 子查询的概念
在上文中我们说过,from后面是可以跟多张表,进行多表查询。但我们我们可能与遇到一些特殊情况,需要对一张表或多张表进行多次select查询。此时就可以采用子查询。
子查询,简单来讲就是将select语句嵌入到其他sql语句中,也叫做嵌套查询。在子查询中,就是依赖于子查询查询出来的结果在一个sql语句中进行二次或多次查询。
根据查询出来的结果,可以将子查询分为单行子查询、多行子查询和多列子查询。
2. 单行子查询
2.1 单行子查询的概念
单行子查询,含义就是子查询只返回一条结果,即返回单列单行数据的子查询。
为了方便大家理解,在这里就举几个使用示例。
2.2 显示和SMITH同一部门的员工
要显示和SMITH同一部分的员工,那我们就首先要拿到SMITH的部门编号。
当我们就拿到了SMITH的部门编号后,就可以拿着这个编号去查询所有部门编号为20的员工了。此时大家可能有两个思路,一个是分为两个sql语句;另一个就是用笛卡尔积进行筛选。但在这里,我们还可以采用子查询的方式。
要查询部门编号为20的员工,就需要使用where子句。如果我们用两条sql语句,第二条sql语句就是用第一条sql语句查出来的结果20进行二次查询:
既然如此,那我们不妨将第一条sql语句放入另一条sql语句中的where子句的判断条件中:
此时我们就拿到了需要的结果了。这就是子查询的使用。注意,在使用子查询时,需要将子select语句用括号括起来,表示将其看做一个整体使用。
可以发现,在上面的这个例子中,子查询只返回了单列单行的deptno=20的数据。这就是单行子查询。
3. 多行子查询
3.1 单行子查询概念
多行子查询,就是指只返回一列,但是有多行数据的子查询。
为了更好的使用这些子查询,就需要使用到一些特殊的关键字。
3.2 in关键字
为了更好的解释in关键字的作用,用一个案例来说明。
案例:查询10号部门的工作岗位相同的雇员的名字、岗位、工资、部门号,但是不包含10号部门自己。
要解决这道题,同样可以用子查询。首先,既然要查询和10号部门的工作岗位相同的雇员信息,那我们首先可以拿到10部门的所有工作岗位。
为了防止出现有员工的工作岗位相同的情况,最好对结果用distinct去重。
有了工作岗位后,就可以将这个结果当做子查询结果,再次查询:
可以看到,此时就出现了报错,告诉我们子查询返回了太多行了。因此,在有多行数据返回情况下,就不能直接将其作为判断条件,而需要使用“in”。
in的作用,大家就可以看成是将单行子查询的结果看做一个集合,只要需要对比的列数据在子查询结果中出现,就为真。即只要在这个集合中出现的数据,外部的子查询都可以认为是真,需要将匹配数据查找出来。
在题目中要求返回雇员的名字、岗位、工资、部门号,但不包括10号部门的。因此,再加上筛选条件和显示内容即可:
在上面sql语句的判断条件中,“<>”就是不等于的意思,和"!="是一样的,在之前的文章中已经介绍过了。
3.3 笛卡尔积与子查询的组合
在上面的内容中,我们通过多行子查询得到了一张新的表,那如果现在我们在上面的题目的基础上,再加一个显示雇员的部门名字的要求呢?
部门名字的数据在dept表中,而上面的多行子查询得到的结果都是在一张表内。但是在上文中讲过,“mysql下一切皆表”。这就意味着,上面的多行子查询出来的结果,其本质依然是一张表。既然是一张表,那就应该可以和其他表做笛卡尔积。测试一下:
我们成功的将empt与子查询的结果做了一次笛卡尔积。注意,对于这种经过查询后得出来的逻辑上的表,在做笛卡尔积时需要用括号括起来将其当做一个整体,并取一个别名,告诉mysql'该表的名字。
既然我们得到了这么一张新表,那我们只需要筛选出里面的正确信息即可。首先因为查询结果中得到的部门号来自于dept表,所以筛选出部门号相同的雇员。然后再显示题目要求的信息即可:
大家在实际写的时候不用换行也可以,这里换行与不换行没有影响,单纯是因为换行比较容易看清这条sql语句的结构而已。
3.4 all关键字
同样的,为了方便大家理解all关键字,也用一个案例来说明。
案例:显示工资比部门30的所有员工的工资高的员工的姓名、工资和部门号。
对于这道题,一种解法就是子查询+聚合函数。这里要求比部门30的所有员工的工资高,那我们只需要用聚合函数找出最大值,然后再用一条select语句查询比这个最大值还大的员工的工资即可:
那如果现在我们不想用聚合函数+单行子查询,而是想用多行子查询呢?
首先,找到表中30号部门的所有人的工资:
然后,我们再用一条select语句进行比对。在这里,就不能使用in关键字了,因为in关键字是比对列数据是否存在于子查询结果,只要有一个存在即为真。但这里是需要对所有结果比对,当列数据大于大于所有的子查询结果才为真。
因此,all关键字的作用就是与子查询的所有结果进行比对,当比对的所有结果同时为真时,才为真。
3.5 any关键字
同样的,举一个案例为大家说明any关键字的作用。
案例:显示工资比部门30的任意员工的工资高的员工的姓名、工资和部门号(包括自己部门的员工)
对于这道题,我们同样可以用聚合函数+单行子查询解决。因为要比部门30的任意员工高,就意味着只需要比部门30的最低工资高即可。
因为可以包含自己部门的员工,就意味着我们甚至不需要筛除同部门员工,很简单。
那如果要用多行子查询呢?
首先,查询部门30的所有员工的工资:
然后,再用一条select语句对该子查询语句的结果进行比对查询。但此时就不能用in和all关键字了,因为in是对比是否存在,all是对子查询的所有结果进行比对,同时满足条件才为真。
此时,就可以使用any关键字:
由此,any关键字的作用就是,与子查询结果的所有结果进行对比,只要有一个满足条件,就为真。
3.6 关键字总结
总结起来,这三个关键字的作用分别为:
(1)in关键字:判断筛选条件是否存在于子查询结果中,存在即为真。
(2)all关键字:判断筛选条件与子查询中的所有结果比对后是否为真,全部结果为真即为真。
(3)any关键字:判断筛选条件与子查询中的任意结果比对后是否为真,只要有一个为真即为真。
4. 多列子查询
4.1 多行子查询的概念
通过上文大家应该知道了,单行子查询就是返回单行单列的数据;多行子查询就是返回多行单列的数据。这两个子查询都是针对于单列情况的。那如果出现查询结果是多列呢?此时就需要使用多列子查询了。
多行子查询,就是指返回多列数据的子查询语句。
为了方便让大家看到实际使用,用一个案例给大家说明。
4.2 查询和SMITH的部门和岗位完全相同的所有雇员,不包含SMITH本人
在这道题中,要求查询和SMITH的部门和岗位完全相同的所有雇员。因此,我们可以先找到SMITH的部门和岗位:
很明显,这里就是两列信息了。此时大家可能就会想用笛卡尔积的方式解决这道题。确实,用笛卡尔积确实可以解决这道题。我们只需要将emp表与这张新形成的表组合起来,然后筛选出其中部门和岗位与SMITH相同的雇员,最后再剔除SMITH即可。
那如果不用笛卡尔积的方式,而是用多列子查询,我们应该如何解决呢?
很简单,在单行和多行子查询中,在where后面的判断我们都只带了一个列数据与子查询结果对比。既然需要对比多个列数据,那我们在与子查询结果比对时,带上多个列数据不就可以了吗?
可以看到,此时就能拿到要求的数据了。注意,在使用多个列数据与子查询结果对比时,需要用括号将列数据括起来。
最后,我们剔除掉SMITH即可:
使用起来也是比较简单的。当然,这里的多列子查询中是多列单行。如果遇到多列多行,只需要在where的判断条件中带上多列子查询的对应关键字即可。
从上面的子查询中我们就可以得出一个结论——在任何时刻查询出来的临时表结构,它的本质也是一张逻辑上的表结构。既然是表结构,我们就可以将其当做一张普通的表进行处理。
因此,在处理mysql中的表时,将所有得到的逻辑表结构都视为一张普通的表的话,处理起来就非常的轻松,就是不断形成一张张普通表进行增删查改。
5. 在from子句中使用子查询
在上面的所有子查询中,都是将子查询放在where子句中充当判断条件。但是,子查询语句也是可以出现在from子句后面的。其原因就是子查询结果形成的临时表也是表结构,而from的作用就是告诉mysql数据库要去哪个表中找数据。因此也可以放在from后面与其他表形成笛卡尔积。
这种使用方法,在上面一些案例中已经使用过了,但并没有仔细讲。
同样的,为了方便大家理解,这里举一个案例进行说明。
5.1 显示每个高于自己所处部门平均工资的部门员工的姓名、部门、工资与平均工资
看到这道题中的自己部门平均工资,大家可能会想到用group by对每个部门分组,然后用聚合函数计算出每个部门的平均工资。通过这种方式,就能够获得每个部门的平均工资了。
但是,这种方法是不可行的。因为当我们获得了每个部门的平均工资后,就需要使用多列子查询。但是,在多列子查询中我们就无法处理了。因为我们知道,where子句是会比select子句先执行的,这就意味着聚合函数得到的平均工资的别名,无法在where中使用。同时,如果我们将聚合函数的值放到where,也会出现报错。由此, 如果采用多列子查询,就很难解决数据比对的问题。
由此,我们就可以采用在from子句中使用子查询的方法。将子查询得到的部门平均工资当做一张表,与emp表做笛卡尔积形成组合。
注意,如果不是数据库中真实存在的表,而是逻辑上的表,就需要带上别名。
有了这张新表后,再来看题目要求。题目中要求是高于自己所处部门的平均工资,因此,这两张表中的部门号应该相同:
最后,要求显示高于自己所处部门的平均工资,因此用emp表的工资与tmp表的平均工资做对比,然后显示要求的数据:
那如果我们再修改一下这道题,要求显示部门名呢?很简单,上面的查询结果不是已经形成了一张表了么。我们拿着这张表再去与保存了部门名的dept表做笛卡尔积,然后筛选出里面部门编号相同的有效数据,再显示出对应数据即可:
看起来很复杂, 其实就是将一张张表组合起来筛选出特定列。
5.2 查找每个部门工资最高的人的姓名、工资、部门、最高工资
这道题,我们同样是看成一张张表来解决。首先,要查找每个部门工资最高的人的信息,因此,先用聚合函数加分组找到他的部门号和工资。
得到这几个部门的部门号和最高工资后,再将这个查询结果看成一张单独的表,与emp表做笛卡尔积:
此时就得到了所有的穷举组合。再比对部门号,找到部门号相同的有效数据:
此时我们就拿到了每个部门的最高工资的人的数据。但是,这张表在经过笛卡尔积后,会有重复的部分。因此,去重加、筛选出工资与最高工资相同的人,然后显示特定数据:
5.3 显示每个部门的信息(部门名,编号,地址)和人员数量
对于这道题,要显示每个部门的信息和人员数量。但是emp表中是没有每个部门的人员数量的。因此,先用聚合函数+分组形成新表:
表有了,但是部门的信息此时不在emp表中,而是在dept表中。因此,将新表与dept表做笛卡尔积:
拿到所有的组合后,筛选出两张表中部门名相同的有效人员:
此时就将题目要求的所有信息拿到了。
总而言之,在mysql的查询中,只需要记住“mysql一切皆表”这句话即可。将一个个复杂的问题划分为对一张张表进行增删查改,就是非常简单的了。而所谓的mysql的“多表查询”,其实并不存在什么多表,永远都是对一张新表做查询。
因此,解决多表问题的本质,就是想办法将多张表转化为一张表。
四、合并查询
在实际中,为了合并多个select的执行结果,可以使用集合操作符union和union all。
union操作符用于取得两个结果集的并集。当使用该操作符后,会自动去掉结果集中的重复行。
union all操作符也是用于取得两个结果集的并集,当使用该操作符后,不会去掉结果集的重复行。
因此,union和union all的区别就是是否会去掉重复行。
1. 案例:查询工资大于2500或职位是MANAGER的人
对于这道题,我们可以将其分为两张表,分别是工资大于2500的表和职位是MANAGER的表。将这两张表形成交集,然后去重就可以得到结果。而union可以自动去重,所以去重工作无需我们自己做:
而如果用union all,就不会去重:
但是要注意,要形成交集,就势必决定了表的列数和表的列名必须是一样的,否则就无法形成并集。
同时这个题目,我们直接在emp表内添加两个判断条件解决:
因此,实际上来说,合并查询基本没什么太大作用,可以被其他查询替代。在实际中几乎很少会用到合并查询。
五、表的内外连接
表的连接分为内连接与外连接,在上面的所有连接方式,其实都是内连接。
1. 内连接
1.1 内连接的含义
内连接实际上就是利用where子句对两张表形成的笛卡尔积进行筛选,前面学习的查询都是以内连接方式查询的,内连接也是在开发过程中使用的最多的连接查询。
内连接的语法也很简单,就是将以前进行笛卡尔积中的连接两个表的逗号替换为inner join,然后把where替换为on。如下图:
虽然我们以前写的都是内连接,但内连接的标准语法如上所示。
1.2 案例:显示SMITH的名字和部门名称
为了让大家看到内连接的实际使用,就举一个例子说明。
这道题很简单,因为部门名称在dept表,而SMITH的名字在emp表,所以需要用笛卡尔积形成新表。在形成新表后,再判断表中数据的部门编号是否相同以获得有效数据。最后在显示内容中填上名字和部门名称即可:
上文中说了,这其实也是内连接。但如果要用内连接的标准语法,就需要将表之间的逗号替换为inner join,where替换为on:
此时就获得了要求的数据。如果还有其他数据,可以用or、and等逻辑运算符连接。当然,也可以继续使用where:
这两种写法是等价的。至于大家想用那种,就因人而异。
2. 外连接
外连接分为左外连接和右外连接。这里分别介绍一下。外连接,其实就是“完全显示表”。
2.1 左外连接
在多表查询中,左侧的表完全显示的话,我们就认为是“左外连接”。这个完全显示指左侧的表不要经过任何筛选,就保持全貌,如果表与表之间的行数据配不上,就算另一张表为空,左侧的表也必须按原貌显示。
语法如下:
其实就是将内连接的inner替换为了left。
为了方便看到现象,我们先创建如下两张表:
然后向这两张表中分别插入如下内容:
在这张表,我们特意将其设置为了两张表的数据无法完全匹配的情况:
由此,我们通过一个案例来测试:
案例:查询所有学生的成绩,如果这个学生没有成绩,也要将这个学生的个人信息显示出来。
在这个题目中,要求完整显示学生个人信息,所以左侧的表就是stu。写出如下sql语句:
如果将左外连接换成内连接,就只会显示id出现在exam中的数据:
2.2 右外连接
当理解了左外连接后,大家就应该理解右外连接了。
右外连接,其实就是在联合查询时,将右侧的表完全显示。
其实右外连接没什么太大意义,毕竟sql语句中表的位置是可以换的,所以遇到右外连接的情况,调换一下表顺序,使用左外连接即可。不过这里还是简单介绍一下。
右外连接的语法如下:
其实就是将左外连接的left替换为了right。
用和左外连接同样的两个表来举个例子。
案例1:查询所有学生的成绩,即时这个成绩没有学生与它对应,也要完全显示。
这道题写起来就非常简单了:
为了方便大家理解,再用上文中使用emp表和dept表再举一个例子。
案例2:列出部门名称和这些部门的员工信息,同时列出没有员工的部门。
首先,部门名称在dept表,员工信息在emp表,所以将这两张表做笛卡尔积,然后筛选出其中部门编号相同的有效数据。
此时就拿到了所有有部门的员工信息了。但是这里要求列出没有员工的部门,即dept表必须完整显示。因此,将逗号替换为right join,where替换为on:
此时就可以看到结果了,部门编号为40的部门中没有员工。当然,也可以用左连接,颠倒一下表的顺序即可。