【MySQL | 基础】多表查询

前文回顾:约束
文章目录
1.多表关系
1.1一对多(多对一)
1.2 多对多关系
1.3 一对一关系
2. 多表查询概述
2.1 多表查询的分类
2.2.1连接查询
2.1.2 字查询
3.内连接
3.1 内连接查询语法
3. 2 代码演示
4. 外连接
4.1 外连接语法
4.2 代码演示
5. 自连接
5.1 自连接语法
5. 2 代码演示
6. 联合查询
7. 子查询
7.1 子查询分类
7.1.1 标量子查询
7.1.2 列子查询
7.1.3 行子查询
7.1.4 表子查询
8. 练习
9.此文章用到的表数据
说明:本文章代码演示和练习使用到的数据表在本文章最后
1.多表关系
项目开发中,进行数据库表结构设计时,会根据业务需求及业务模块之间的关系,分析并设计表结构,以各个表结构之间有存在着各种联系,基本上分为三种:
- 一对多(多对一)
- 多对多
- 一对一
1.1一对多(多对一)
- 案例:部门 与 员工的关系
- 关系:一个部门对应多个员工,一个员工对应一个部门
- 实现:在多的一方建立外键,指向一的一方的主键

1.2 多对多关系
- 案例:学生 与 课程的关系
- 关系:一个学生可以选修多门课程,一门课程也可以供多个学生选择
- 实现:建立第三张中间表,中间表至少包含两个外键,分别关联两个主键。

create table student(
id int auto_increment primary key comment '主键ID',
name varchar(10) comment '姓名',
no varchar(10) comment'学号,'
)comment'学生表';
insert into student values (null,'黛绮丝','2000100101'),(null,'谢逊','2000100102'),(null,'般天正','2000100103'),(null,'韦一笑','2000100104');create table course(
id int auto_increment primary key comment '主键ID',
name varchar(10) comment '课程名称'
)comment '课程表';
insert into course values (null,'Java'), (null,'PHP'), (null,'MySL') ,(null,'Hadoop');create table student_course(
id int auto_increment comment '主键' primary key,
studentid int not null comment '学生ID!',
courseid int not null comment '课程',
constraint fk_courseid foreign key (courseid)
references course (id),
constraint fk_studentid foreign key (studentid)references student (id)
)comment '学生课程中间表';
insert into student_course values (null,1,1),(null,1,2),(null,1,3),(null,2,2),(null,2,3),(null,3,4);

1.3 一对一关系
- 案例:用户 与 用户 详情的关系
- 关系:一对一关系,多用于单表的拆分,将一张表的基础字段放在一张中,其他详细字段放在另一张表中,以提升操作效率
- 实现:在任意的一方加入外键,关联另一方的主键,并且设置外键为唯一的(UNIQUE)


CREATE TABLE tb_user (id INT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',name VARCHAR(10) COMMENT '姓名',age INT COMMENT '年龄',gender CHAR(1) COMMENT '1: 男, 2: 女',phone CHAR(11) COMMENT '手机号'
) COMMENT '用户基本信息表';CREATE TABLE tb_user_edu (id INT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',degree VARCHAR(20) COMMENT '学历',major VARCHAR(50) COMMENT '专业',primaryschool VARCHAR(50) COMMENT '小学',middleschool VARCHAR(50) COMMENT '中学',university VARCHAR(50) COMMENT '大学',userid INT UNIQUE COMMENT '用户ID',CONSTRAINT fk_userid FOREIGN KEY (userid) REFERENCES tb_user(id)
) COMMENT '用户教育信息表';INSERT INTO tb_user(id, name, age, gender, phone) VALUES(NULL, '黄渤', 45, '1', '18800011111'),(NULL, '冰冰', 35, '2', '18800022222'),(NULL, '马云', 55, '1', '18800088888'),(NULL, '李彦宏', 50, '1', '18800099999');INSERT INTO tb_user_edu(id, degree, major, primaryschool, middleschool, university, userid) VALUES(NULL, '本科', '舞蹈', '静安区第一小学', '静安区第一中学', '北京舞蹈学院', 1),(NULL, '硕士', '表演', '朝阳区第一小学', '朝阳区第一中学', '北京电影学院', 2),(NULL, '本科', '英语', '杭州市第一小学', '杭州市第一中学', '杭州师范大学', 3),(NULL, '本科', '应用数学', '阳泉区第一小学', '阳泉区第一中学', '清华大学', 4);
2. 多表查询概述
- 概述:指的是从多张表中查询数据
- 笛卡尔积:笛卡尔积是指在数学中,两个集合A 和 集合B的所有组合情况(在多表查询时,需要消除无效的笛卡尔积)比如:R 表和 T进行笛卡尔积运算 得到的结果行数等于 T行数 乘以 S行数,列数等于T列数 加 S列数。

所需要的数据表在文章最后准备数据数据中
-- 多表查询 -- 笛卡尔积
select * from emp,dept;-- 消除无效的笛卡尔积
select * from emp,dept where emp.dept_id = dept.id ORDER BY emp.id; -- 发现查询到了16 条数据,原因id为17的员工没有分配部门,部门为null。
2.1 多表查询的分类
2.2.1连接查询
- 内连接:相当于查询A, B交集部分的数据。
- 外连接:
- 左外连接:查询左表所有数据,以及两张表交集部分的数据
- 右外连接:查询右表所有数据,以及两张表交集部分的数据
- 自连接:当前表自身的连接查询,自连接必须使用表别名
2.1.2 字查询
3.内连接
注意:内连接连接的是两张表交集部分。
3.1 内连接查询语法
隐式内连接
SELECT 字段列表 FROM 表1, 表2 WHERE 条件...;
显示内连接
SELECT 字段列表 FROM 表1 [INNER] JOIN 表2 ON 连接条件...;
3. 2 代码演示
-- 内连接演示
-- 1. 查询每一个员工的姓名,以及关联的部门的名称(隐式内链接)
-- 表结构 : emp, dept
-- 连接条件: emp.dept_id = dept.id
select emp.name as 姓名, dept.name as 部门 from emp, dept where emp.dept_id = dept.id; -- 给表起一个别名(注意:如果起了别名就不能再使用别名,原来的名字就不能使用了)
select e.name as 姓名, d.name as 部门 from emp e, dept d where e.dept_id = d.id; -- 2. 查询每一个员工的姓名,以及关联的部门的名称(显示内链接)
-- 表结构 : emp, dept
-- 连接条件: emp.dept_id = dept.id
select emp.name as 姓名, dept.name as 部门 from emp inner join dept on emp.dept_id = dept.id;
4. 外连接
4.1 外连接语法
左外连接
SELECT 字段列表 FROM 表1 LEFT [OUTER] JOIN 表2 ON 条件...;
注意:左外连接相当于查询表1(左表)的所有数据 包含 表1和表2交集部分的数据。
右外连接
SELECT 字段列表 FROM 表1 RIGHT [OUTER] JOIN 表2 ON 条件...;
注意:右外连接相当于查询表2(右表)的所有数据 包含 表1和表2交集部分的数据。
4.2 代码演示
-- 外连接演示
-- 1、查询emp表的所有数据,和对应的部门信息(左外连接)
-- 表结构: emp, dept
-- 连接条件:emp.dept_id = dept.id
select e.*,d.name from emp e left outer join dept d on e.dept_id = d.id;
select e.*,d.name from emp e left join dept d on e.dept_id = d.id; -- 2、查询dept表的所有数据,和对应的员工信息(右外连接)
select d.*,e.* from emp e right outer join dept d on e.dept_id = d.id;
select d.*,e.* from dept d left outer join emp e on e.dept_id = d.id;
5. 自连接
- 自连接:当前表自身的连接查询,自连接必须使用表别名
5.1 自连接语法
SELECT 字段列表 FROM 表A 别名A JOIN 表B 别名B ON 条件...;
注意:自连接查询,可以是内连接查询也可以是外连接查询。
5. 2 代码演示
-- 自连接
-- 1、查询 员及其所属领导名字
-- 表结构:emp表
-- emp表中managerid所属领导的id
select e1.name,e2.name as 领导 from emp e1 inner join emp e2 on e1.managerid = e2.id;-- 2.查询所有员工emp 以及领导的名字emp 如果员工没有领导也需要查询出来
select e1.name 员工, e2.name 领导 from emp e1 left outer join emp e2 on e1.managerid = e2. id;
6. 联合查询
- 联合查询设计到的两个关键字:union, union all
- 对于联合查询,就是把多次查询结果合并一起,形成一个新的查询结果集。
- 注意:
- 对于联合查询的多张表的列数必须保持一致,字段类型也需要保持一致。
- union all 会将全部的数据直接合并在一起,union会对合并之后的数据去重。
SELECT 字段列表 FROM 表A ...
UNION [ALL]
SELECT 字段列表 FROM 表B ...;
-- union all, union
-- 联合查询
-- 1、将薪资低于 5000 的员工,和 年龄大于 50 岁的员工全部查询出来。
select * from emp where salary < 5000
union all
select * from emp where age > 50;-- 去重只需要把all去掉即可
select * from emp where salary < 5000
union
select * from emp where age > 50;select * from emp where salary < 5000 or age > 50;
7. 子查询
概念:SQL语句中嵌套SELECT语句,称为嵌套查询,又称子查询。
SELECT * FROM 表1 WHERE 字段名 = (SELECT 字段名 FROM 表2);
注意:子查询外部的语句可以是INSERT、UPDATE、DELETE、SELECT 的任何一个。
7.1 子查询分类
根据子查询结果不同,分为:
- 标量子查询(子查询的结果为单值)
- 列子查询(子查询结果为一列)
- 行子查询(子查询结构为一行)
- 表子查询(子查询结果为多行多列)
根据子查询位置,分为:WHERE之后,FROM之后、SELECT之后。
7.1.1 标量子查询
- 标量子查询:子查询返回结果是单个值(数字、字符串、日期),最简单的形式,这种子查询称为标量子查询。
- 常用的操作符号:= <> < <= > >=
-- 标量子查询
-- 1、查询“销售部”的所有员工信息
-- 拆分题目:
-- a:查询“销售部”部门ID
select id from dept where name = '销售部';
-- b: 根据销售部门id查询员工信息
select * from emp where dept_id = 4;-- 子查询查询如下:
select * from emp where dept_id = (select id from dept where name = '销售部') -- 2、查询在“方东白”入职之后的员工信息
select * from emp where entrydate > (select entrydate from emp where name = '方东白');
7.1.2 列子查询
- 列子查询:子查询返回的结果是一列(可以是多行),这种子查询称为列子查询。
- 常用的操作符:IN、NOT IN、ANY、SOME、ALL
| 操作符 | 描述 |
| IN | 在指定范围之内,多选一 |
| NOT IN | 不在指定的集合范围内 |
| ANY | 子查询返回列表中,有任意一个满足即可 |
| SOME | 与ANY等同,使用SOME的地方都可以使用ANY |
| ALL | 子查询返回列表的所有值都必须满足 |
-- 列子查询
-- 1、查询”销售部“和“市场部”的所有员工信息
-- a:查询”销售部“和“市场部”的部门id
select id from dept where name = '销售部' or name = '市场部';
-- b:根据部门id,查询员工信息
select * from emp where dept_id in (2, 4);select * from emp where dept_id in (select id from dept where name = '销售部' or name = '市场部');-- 2、查询比 财务部 所有人工资都高的员工信息
-- a:查询所有财务部人员工资
select salary from emp where dept_id = (select id from dept where name = '财务部');
-- b:查询比”财务部“薪资都高的员工信息
select * from emp where salary > all(select salary from emp where dept_id = (select id from dept where name = '财务部'));-- 方式2:
select * from emp where salary > (select max(salary) from emp where dept_id = (select id from dept where name = '财务部'));-- 3、查询比“研发部”其中任意一人工资高的员工信息
-- a:查询研发部门所有人的工资
select salary from emp where dept_id = (select id from dept where name = '研发部');
-- b:比“研发部”任意一人工资要高的员工信息
select * from emp where salary > any(select salary from emp where dept_id = (select id from dept where name = '研发部'));-- 方式2
select * from emp where salary > some(select salary from emp where dept_id = (select id from dept where name = '研发部'));
-- 方式3
select * from emp where salary > (select min(salary) from emp where dept_id = (select id from dept where name = '研发部'));
7.1.3 行子查询
- 行子查询:子查询返回的结果是一行(可以是多列),这种子查询称为行子查询。
- 常用的操作法:= 、<>、IN、NOT IN
-- 行子查询
-- 1、查询与“张无忌”的薪资及直属领导相同的员工信息
-- a:查询与“张无忌”的薪资及直属领导
select salary, managerid from emp where name = '张无忌';
-- b:查询与“张无忌”的薪资及直属领导相同的员工信息
select * from emp where (salary,managerid) = (select salary, managerid from emp where name = '张无忌');
7.1.4 表子查询
- 表子查询:子查询返回的结果是多行多列,这种子查询称为表子查询。
- 常用的操作符:IN
-- 表子查询
-- 1、查询与“鹿杖客”,“宋远桥”职位和薪资相同的员工信息
-- a:查询与“鹿杖客”,“宋远桥”职位和薪资
select job, salary from emp where name = '鹿杖客' or name = '宋远桥';
-- b:查询与“鹿杖客”,“宋远桥”职位和薪资相同的员工信息
select * from emp where (job,salary) in (select job, salary from emp where name = '鹿杖客' or name = '宋远桥');-- 2、查询入职日期是“2006-01-01”之后的员工信息,及其部门
-- a:查询入职日期是“2006-01-01”之后的员工信息
select * from emp where entrydate > '2006-01-01';
-- b:查询这部分员工,对应的部门信息
select * from (select * from emp where entrydate > '2006-01-01') e left outer join dept d on e.dept_id = d.id;-- 方式2
select * from emp e left outer join dept d on e.dept_id = d.id where entrydate > '2006-01-01';
8. 练习

-- 1、查询员工的姓名,年龄,职位,部门信息(隐式查询)
-- 表:emp 、dept
-- emp.dept_id = dept.id
select e.name,age,job,d.name from emp e, dept d where e.dept_id = d.id;
select e.name,age,job,d.name from emp e inner join dept d on e.dept_id = d.id; -- 显示内连接-- 2、查询年龄小于30岁的员工的姓名,年龄,职位,部门信息(显示内连接)
-- 表:emp 、dept
-- emp.dept_id = dept.id
select e.name,age,job,d.name from emp e inner join dept d on e.dept_id = d.id where age < 30;-- 3、查询拥有员工的部门ID 、部门名称
-- 表:emp 、dept
-- emp.dept_id = dept.id
select distinct dept_id,d.name from emp e inner join dept d on e.dept_id = d.id;-- 4、查询所有年龄大于40岁的员工,以其归属的部门名称,如果员工没有分配部门,也需要显示出来
-- 表:emp 、dept
-- emp.dept_id = dept.id
-- 外连接
select e.*,d.name from emp e left outer join dept d on e.dept_id = d.id where age > 40;-- 5、查询所有员工资等级
-- 表:emp、salgrade
-- 连接条件:emp.salary >= salgrade.losal and emp.salary <= salgrade.hisal
select e.*,s.grade 薪资等级 from emp e ,salgrade s where salary >= losal and salary <= s.hisal;
select e.*,s.grade 薪资等级 from emp e ,salgrade s where salary between s.losal and s.hisal;-- 6、查询“研发部”所有员工的信息以及 工资等级
-- 表:emp、dept、salgrade
-- 连接条件:emp.dept_id dept.id and e.salary between s.losal and s.hisal
-- 查询条件:dept.name = '研发部'
select e.*,s.grade from emp e,salgrade s, dept d where e.dept_id = d.id and e.salary between s.losal and s.hisal and d.name = '研发部';-- 7、查询“研发部”员工的平均工资
-- 表:emp.dept
-- 连接条件:e.dept_id = d.id
-- 查询条件:d.name = '研发部'
select avg(salary) from emp e,dept d where e.dept_id = d.id and d.name = '研发部';-- 8、查询工资比“灭绝”高的员工信息
-- 子查询
select * from emp where salary > (select salary from emp where name = '灭绝');
-- 自身连接
select e1.* from emp e1, emp e2 where e2.name = '灭绝' and e1.salary > e2.salary;-- 9、查询比平均薪资高的员工信息
-- 子查询
select * from emp where salary > (select avg(salary) from emp);-- 10、查询低于本部门平均工资的员工信息
-- a: 查询指定部门平均薪资
select avg(e1.salary) from emp e1 where e1.dept_id = 1;
select avg(e1.salary) from emp e1 where e1.dept_id = 2;
-- b:查询低于本部门平均工资的员工信息
select * from emp e2 where e2.salary < (select avg(e1.salary) from emp e1 where e1.dept_id = e2.dept_id);-- 11、查询所有的部门信息,并统计部门的员工人数
-- a:查询所以部门信息
select * from dept;
-- b:统计每个部门的员工人数
select count(*) from emp where dept_id = 1;
-- 合并上面两部
select d.id, d.name ,(select count(*) from emp e where e.dept_id = d.id) from dept d-- 12、查询所有学生的选课情况,展示出学生名称,学号,课程名称
-- 表:student,course、sc
-- 连接条件:s.id = sc.studentid and s.id = sc.courseid
select s.name,no,c.name from student s ,course c, student_course sc where s.id = sc.studentid and c.id = sc.courseid;
9.此文章用到的表数据
多表查询代码演示以及练习数据
create table dept(id int primary key auto_increment,name varchar(10)
);insert into dept values(null, '研发部'),(null, '市场部'),(null, '财务部'),(null, '销售部'),(null, '总经办'),(null, '人事部');create table emp(id int primary key auto_increment,name varchar(10),age int,job varchar(10),salary int,entrydate date,managerid int,dept_id int,constraint fk_dept foreign key (dept_id) references dept(id)
);insert into emp values(null, '金庸', 66, '总裁', 20000, '2000-01-01', null, 5),(null, '张无忌', 20, '项目经理', 12500, '2005-12-05', 1, 1),(null, '杨逍', 33, '开发', 8400, '2000-11-03', 2, 1),(null, '韦一笑', 48, '开发', 11000, '2002-02-05', 2, 1),(null, '常遇春', 43, '开发', 10500, '2004-09-07', 3, 1),(null, '小昭', 19, '程序员鼓励师', 6600, '2004-10-12', 2, 1),(null, '灭绝', 60, '财务总监', 8500, '2002-09-12', 1, 3),(null, '周芷若', 19, '会计', 4800, '2006-06-02', 7, 3),(null, '丁敏君', 23, '出纳', 5250, '2009-05-13', 7, 3),(null, '赵敏', 20, '市场部总监', 12500, '2004-10-12', 1, 2),(null, '鹿杖客', 56, '职员', 3750, '2006-10-03', 10, 2),(null, '鹤笔翁', 19, '职员', 3750, '2007-05-09', 10, 2),(null, '方东白', 19, '职员', 5500, '2009-02-12', 10, 2),(null, '张三丰', 88, '销售总监', 14000, '2004-10-12', 1, 4),(null, '俞莲舟', 38, '销售', 4600, '2004-10-12', 14, 4),(null, '宋远桥', 40, '销售', 4600, '2004-10-12', 14, 4),(null, '陈友谅', 42, null, 2000, '2011-10-12', 1, null);
create table salgrade(grade int,losal int,hisal int
) comment'薪资等级表';insert into salgrade
values (1, 0, 3000),(2, 3001, 5000),(3, 5001, 8000),(4, 8001, 10000),(5, 10001, 15000),(6, 15001, 20000),(7, 20001, 25000),(8, 25001, 30000);
多表查询联系第12题所需要的表
create table student(
id int auto_increment primary key comment '主键ID',
name varchar(10) comment '姓名',
no varchar(10) comment'学号,'
)comment'学生表';
insert into student values (null,'黛绮丝','2000100101'),(null,'谢逊','2000100102'),(null,'般天正','2000100103'),(null,'韦一笑','2000100104');create table course(
id int auto_increment primary key comment '主键ID',
name varchar(10) comment '课程名称'
)comment '课程表';
insert into course values (null,'Java'), (null,'PHP'), (null,'MySL') ,(null,'Hadoop');create table student_course(
id int auto_increment comment '主键' primary key,
studentid int not null comment '学生ID!',
courseid int not null comment '课程',
constraint fk_courseid foreign key (courseid)
references course (id),
constraint fk_studentid foreign key (studentid)references student (id)
)comment '学生课程中间表';
insert into student_course values (null,1,1),(null,1,2),(null,1,3),(null,2,2),(null,2,3),(null,3,4);

