hello~欢迎来到我的MySQL笔记系列。
在实际我们对数据库中的表查询中,往往面对的对象并不是一个,而是多个。比如一个成绩表上标注的课程号,并没有标注课程名,我们想要通过成绩表上知道课程名的化就要查询课程表。
所以本篇笔记我记录的是在MySQL下的多表查询、嵌套查询、以及多表连接时的内外连接问题。
注意,本篇使用的测试表为Oracle 9i经典测试表:scott_data.sql(使用source恢复数据库即可:scott-(dept部门表、emp员工表、salgrade工资等级表))
我的上一篇mysql笔记:
【MySQL】内置函数_柒海啦的博客-CSDN博客
目录
一、多表查询
笛卡儿积
-显示雇员名、雇员工资以及所在部门的名字
-显示部门号为10的部门名,员工名和工资
-显示各个员工的姓名,工资,及工资级别
多表查询流程
二、自连接
-显示员工FORD的上级领导的编号和姓名
三、子查询
1.单行子查询
2.多行子查询
-in
-all
-any
3.多列子查询
4.相关or不相关子查询
5.在from中使用子查询
6.集合查询
union
四、内外连接
1.内连接
2.外连接
左外连接
右外连接
全外连接
想要了解多表查询,那么不同的表之间应该是存在联系的。比如外键:从表中的某个属性是来源于主表的唯一键约束(主键或者唯一键--唯一属性)。
通过外键我们就可以有效的进行连接起来。但是在有效之前,我们可以简单暴力的将两个表组合起来:
而简单暴力的将两个表连接起来就是笛卡尔积。也就是说,将一张表的每一个记录和另一张表的每一个记录均合并起来,所以记录的总个数就是两个表的个数之积。
select ... from table1 [, table2...];
比如,我们将emp员工表和dept部门表用笛卡尔积连接起来:
所以,笛卡尔积的本质就是数据的穷举,可以类似于平时的多重暴力遍历。
那么现在既然都连接成一张表了,所谓的多表查询是不是就转换为之前的单表查询了呢。
因为要求所在部门的名字,那么需要将emp表和dept表进行关联起来。
我们首先自然可以通过笛卡尔积将两个表进行连接。但是直接连接的化会出现很多的无效数据,因为emp表的deptno和dept表的deptno没有对应起来。这个时候就可以通过where筛选条件进行对应即可。筛选出来的合并的表就是一个有效表了,就可以进行查询数据了。
select ename, sal, dname from emp, dept where emp.deptno=dept.deptno;
需要注意,因为是两张表合并,如果出现重复属性名,前面必须指定表名或者重命名。
因为要显示部门号为10的部门名,所以需要emp表和dept表连接。
select emp.deptno, dept.dname, ename, sal
from emp, dept
where emp.deptno=dept.deptno and emp.deptno=10;
因为存在工资级别,那么就是emp表和salgrade表进行连接筛选。
这次筛选,需要根据salgrade表中的限制对sal工资进行匹配,从而达到多表查询的目的。
select ename, sal, grade from emp, salgrade where losal <= sal and sal <= hisal;
--或者:select ename, sal, grade from emp, salgrade where sal between losal and hisal;
综上,我们解决多表查询的思路如下:
1.先读题,确定都和哪些表有关。
2.组合形成一张表。
3.将多表查询,看做为一张表的查询。
即然是多表查询,那么多张表可不可以是本身呢?
在一些场景中,我们需要自身连接才能达到我们要查找的目的。但是自连接为了区分不同表,需要进行重命名。
select ... from table [as] t1 [, table [as] t2...];
因为上级领导的编号就是此表中员工的编号。所以想要找到对应员工的上级领导的可以做一个自连接进行查找:
select t1.ename 员工, t1.mgr 领导编号, t2.ename 领导
from emp t1, emp t2
where t1.mgr=t2.empno and t1.ename='FORD';
当然,如果不想让两个表连接查找的化,我们可以嵌套查询去寻找:
select empno 领导编号, ename 领导
from emp
where empno=(select mgr from emp where ename='FORD');
嵌套查询也就是我们马上要说的父子查询。
子查询就是指select语句嵌套在其他sql的select语句中。此时针对于外select就是父查询,内部select就是子查询。
对于子查询,可以不断嵌套,执行顺序类似于递归函数(栈)。并且对于子查询是否可以依靠父查询的表分为相关子查询和不相关子查询;对于子查询返回的结果也可以分为单行结果、多行结果。并且每一个记录的列也可以是多列还是单列。
并且select语句返回结果和也可以和表构成笛卡尔积。通常为了缓解两表直接合并在一起很大,缓解where的压力。
select ...
from table1, [(select ...) [as] s1, ...]
where expression(select ...), ...
[group by column] [having ...]
[order by column] [limit ...];
下面我们使用测试表对上面的结论进行测试。
单行子查询返回的值只是存在一条记录(筛选一条记录)。此时where表达式中可以使用 = 或者in进行判断。
-显示SMITH同一部门的员工
select ename, deptno from emp where deptno=(select deptno from emp where ename='SMITH');
因为多行子查询返回的结果就是多条记录。那么在父查询中的where表达式中需要借助in、all、any等关键字进行查询,就不能单单的使用=了。
子查询为单列结果,并且父查询where条件是结果的子集即可。
-查询和10号部门的工作岗位相同的雇员的名字,岗位,工资,部门号,但是不包含10自己的
select ename, job, sal, deptno
from emp where job in (select distinct job from emp where deptno=10)
and deptno <> 10;
in一般用于查询是否和子查询结果存在相同的,存在就筛选出来。(in也可以替代=符号哦)
all全部的意思。也就是说父查询where表达式中的条件必须满足要么大于子查询结果值得全体,要么就是小于全体。
也就是说,a > all(b) 相当于:a > max(b);a < all(b) 相当于 a < min(b);
-显示工资比部门30的所有员工的工资高的员工的姓名、工资和部门号
select ename, sal, deptno
from emp where sal > all(select sal from emp where deptno=30);
any是任何的意思。也就是说父查询where表达式中的条件必须满足要么不能大于子查询结果全体值,要么就是不能小于全体。
翻译一下上面:a>any(b) -> a>min(b); a -显示工资比部门30的任意员工的工资高的员工的姓名、工资和部门号(包含自己部门的员工) 注意到上面父查询的where表达式条件都是单个属性进行筛选的。那么如果是多条属性呢?我们就需要多个属性加入条件中进行筛选。 -查询和SMITH的部门和岗位完全相同的所有雇员,不含SMITH本人 当然in也没有问题。只不过是将单个属性换成了多个属性而已。 相关子查询的查询条件依赖于父查询,首先取外层查询中表的第一个元组,根据它与内层查询相关的属性值处理内层查询,若WHERE子句返回值为真,则取此元组放入结果表;然后再取外层表的下一个元组,重复这一过程,直至外层表全部检查完为止。 不相关子查询的查询条件不依赖于父查询,由里向外逐层处理。即每个子查询在上一级查询处理之前求解,子查询的结果用于建立其父查询的查找条件。 向我们之前所写的所有select语句都不是相关子查询。下面举一个例子表示相关子查询: -查询emp表中比自己当前部门工资平均值大的所有员工。 -相关子查询: -不相关子查询: 可以看到,相关子查询在特定的场合下十分优雅,都不用进行连接查询了。 其实第二个不相关子查询就是利用的在from中使用select进行笛卡尔积操作合并表的。 子查询的语句不仅仅可以出现在where子句中,同样可以出现在from子句中作为临时表于其他表进行笛卡尔积。 使用这个技巧可以减轻where筛选的压力。 -显示每个高于自己部门平均工资的员工的姓名、部门、工资、平均工资 -查找每个部门工资最高的人的姓名、工资、部门、最高工资 -显示每个部门的信息(部门名,编号,地址)和人员数量 另外,对于两个表如果属性相同(列和数据类型均相同),我们可以进行集合查询操作。 集合查询分为三种:并操作:union,交操作:intersect,差操作:except; 对于并操作来说就是合并到一起,并且自动排除重复项(保留加上all即可),交操作就是两个查询结果的相同部分,差操作就是第一个结果减去第二个相同的结果。 需要注意,老版本mysql不支持交操作共和差操作。sql server是支持的。 用以下的例子进行加深理解: -将工资大于2500或职位是MANAGER的人找出来 首先是自动排除重复项 不排除重复项 内连接就是利用where子句对两种表形成的笛卡儿积进行筛选,我们前面学习的查询都是内连 比如我们创建两张表放在我们的测试数据库test2_db;一张学生姓名和学号-学生表。一张学号和对应的成绩-成绩表。创建语句如下: 创建学生表Stu: 创建成绩表Grade: 现在对两张表简单插入如下的数据: 我们按照内连接走,匹配的才会显示记录(之前不带这些关键字都是默认的效果) 通常可以使用on来表示对表连接的匹配限制,where筛选记录。 比如我们将上面的表将对应的学生和成绩匹配起来: 外连接就是表示可以将两张或者多个表没有匹配的也加入里面。没有匹配的对应属性会填上空。外连接分为左外连接和右外连接。全外连接可以使用集合查询union进行实现。 左外连接会连接左边table1表中没有匹配的记录。 我们使用左外连接连接上面的表Stu、Grade,看看sno为NULL,sname为小明的会不会展示出来: 右外连接会连接右边table2表中没有匹配的记录。 我们使用右外连接连接上面的表Stu、Grade,看看sno为NULL,grade为99的会不会展示出来: 全外连接也就是将左右两表没有匹配成功的记录也展示出来,没有的属性会填上NULL。mysql中可以使用合并查询实现: select ename, sal, deptno
from emp where sal > any(select sal from emp where deptno=30);
3.多列子查询
select ename
from emp where (deptno, job)=(select deptno, job from emp where ename='SMITH')
and ename <> 'SMITH';
4.相关or不相关子查询
select ename, sal, deptno from emp e1
where sal > (select avg(sal) from emp e2 where e1.deptno=e2.deptno)
order by deptno;
select ename, sal, emp.deptno
from emp, (select deptno, avg(sal) 平均工资 from emp group by deptno) s1
where emp.deptno=s1.deptno and sal > 平均工资 order by deptno;
5.在from中使用子查询
select ename, deptno, sal, format(asal, 2) 平均工资
from emp, (select deptno dtno, avg(sal) asal from emp group by dtno) tmp
where deptno=dtno and sal > asal
order by deptno;
select emp.ename, emp.sal, emp.deptno, 最高工资
from emp, (select deptno, max(sal) 最高工资 from emp group by deptno) tmp
where emp.deptno=tmp.deptno and sal=最高工资;
select dept.deptno, dname, loc, 人员数量
from dept, (select deptno, count(*) 人员数量 from emp group by deptno) tmp
where dept.deptno=tmp.deptno;
6.集合查询
select ...
[集合操作]
select ...
union
select ename, sal, job from emp where sal>2500
union
select ename, sal, job from emp where job='MANAGER';
select ename, sal, job from emp where sal>2500
union all
select ename, sal, job from emp where job='MANAGER';
四、内外连接
1.内连接
接,也是在开发过程中使用的最多的连接查询。如果一张表中一条记录出现NULL是不会进行连接显示的。create table Stu(
name varchar(11) not null,
sno int(10) zerofill auto_increment,
);
create table Grade(
sno int(10) unsigned,
grade float(4, 1),
);
-- Stu
insert into Stu values ('张三', 2100000000), ('李四', 2100000001), ('王五', 2100000002), (NULL, 2100000003), ('小明', NULL);
-- Grade
insert into Grade values (2100000000, 89.5), (2100000001, NULL), (2100000002, 45.5), (2100000003, 97), (NULL, 99);
select ... from table1 inner join table2... on... where ...;
select * from Stu inner join Grade on Stu.sno=Grade.sno;
2.外连接
左外连接
select ... from table1 left join table2... on... where ...;
select * from Stu left join Grade on Stu.sno=Grade.sno;
右外连接
select ... from table1 right join table2... on... where ...;
select * from Stu right join Grade on Stu.sno=Grade.sno;
全外连接
select * from Stu left join Grade on Stu.sno=Grade.sno
union
select * from Stu right join Grade on Stu.sno=Grade.sno;