一、数据库多表关系设计
项目中业务数据存在关联,表与表分为三种关系:一对多、多对多、一对一,不同关系建表规则不同。
1.1 一对多(最常用)
业务场景
部门表 & 员工表:
- 一个部门包含多名员工
- 一名员工仅归属一个部门
建表规则
在多的一方(员工表) 添加外键,关联一的一方(部门表)主键。
1.2 多对多
业务场景
学生表 & 课程表:
- 一名学生可选多门课程
- 一门课程可被多名学生选择
建表规则
新增中间关联表,中间表至少两个外键,分别关联两张业务表主键。
sql
sql
-- 学生课程中间表(多对多关联)
create table student_course(
id int auto_increment comment '主键' primary key,
studentid int not null comment '学生ID',
courseid int not null comment '课程ID',
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唯一约束,保证一对一。
sql
sql
-- 用户基本信息表
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 '用户基本信息表';
-- 用户教育信息表(一对一,userid加unique约束)
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','18800001111'),
(null,'冰冰',35,'2','18800002222'),
(null,'码云',55,'1','18800008888'),
(null,'李彦宏',50,'1','18800009999');
-- 插入用户教育详情数据
insert into tb_user_edu(id, degree, major, primaryschool, middleschool, university, userid) values
(null,'本科','舞蹈','静安区第一小学','静安区第一中学','北京舞蹈学院',1),
(null,'硕士','表演','朝阳区第一小学','朝阳区第一中学','北京电影学院',2),
(null,'本科','英语','杭州市第一小学','杭州市第一中学','杭州师范大学',3),
(null,'本科','应用数学','阳泉第一小学','阳泉区第一中学','清华大学',4);
二、多表查询基础概念
2.1 笛卡尔积
直接查询多张表不加条件,会生成两张表所有数据无序组合,大量无效数据:
sql
sql
-- 产生笛卡尔积(错误写法)
select * from emp, dept;
2.2 消除笛卡尔积
添加关联等值条件,只查询存在业务关联的数据:
sql
sql
-- 正确:通过部门id关联,过滤无效数据
select * from emp, dept where emp.dept_id = dept.id;
2.3 多表查询四大分类
- 内连接:查询两张表交集数据
- 外连接:左外连接、右外连接(保留单表全部数据)
- 自连接:一张表当做两张表关联查询,必须设置表别名
- 联合查询:
union / union all合并多条查询结果 - 子查询:
select嵌套查询,分为标量 / 列 / 行 / 表子查询
三、内连接 INNER JOIN
只返回两张表满足关联条件的交集数据,分为隐式内连接 、显式内连接。
3.1 隐式内连接(逗号分隔表,where 写条件)
sql
sql
-- 基础写法
select emp.name, dept.name from emp, dept where emp.dept_id = dept.id;
-- 别名简化写法(推荐)
select e.name, d.name from emp e, dept d where e.dept_id = d.id;
3.2 显式内连接(JOIN ... ON 标准写法,可读性更强)
sql
sql
-- 查询员工姓名+所属部门名称
select e.name, d.name
from emp e
inner join dept d
on e.dept_id = d.id;
说明:
inner关键字可省略,仅写join默认代表内连接。
四、外连接 LEFT / RIGHT JOIN
4.1 左外连接 LEFT OUTER JOIN
保留左表全部数据 ,右表无匹配数据填充null,开发最常用。
sql
sql
-- 查询所有员工,附带对应部门信息,无部门员工也保留
select e.*, d.name
from emp e
left outer join dept d
on e.dept_id = d.id;
4.2 右外连接 RIGHT OUTER JOIN
保留右表全部数据 ,左表无匹配数据填充null。
sql
sql
-- 查询所有部门,附带对应员工信息,无员工部门也保留
select d.*, e.*
from emp e
right outer join dept d
on e.dept_id = d.id;
说明:
outer关键字可省略,left join/right join等价。
五、自连接(同表关联)
一张表通过别名拆分成两张虚拟表,实现自身关联查询,必须定义表别名。
场景 1:内连接查询员工及直属领导(无领导员工不展示)
sql
sql
select a.name as 员工, b.name as 领导
from emp a, emp b
where a.managerid = b.id;
场景 2:左外连接查询所有员工,无领导员工也展示
sql
sql
select a.name as 员工, b.name as 领导
from emp a
left join emp b
on a.managerid = b.id;
六、联合查询 UNION / UNION ALL
合并多条select查询结果,要求多条查询字段数量、字段类型完全一致。
union all:直接合并所有数据,不去重,效率更高union:合并后自动去重排序,性能略差
sql
sql
-- 需求:查询薪资低于5000 或 年龄大于50的员工
-- union all(不去重)
select * from emp where salary < 5000
union all
select * from emp where age > 50;
-- union(自动去重)
select * from emp where salary < 5000
union
select * from emp where age > 50;
七、子查询(嵌套 SELECT)
SQL 语句中嵌套select查询,外层可为select / insert / update / delete。
子查询分类(按返回结果划分)
- 标量子查询:返回单个值(一行一列)
- 列子查询:返回一列多行
- 行子查询:返回一行多列
- 表子查询:返回多行多列
7.1 标量子查询(返回单个值)
示例 1:查询销售部全部员工
sql
sql
-- 分步:1.查销售部id 2.根据id查员工
select id from dept where name = '销售部';
select * from emp where dept_id = 4;
-- 合并为标量子查询
select * from emp where dept_id = (select id from dept where name = '销售部');
示例 2:查询比方东白入职晚的员工
sql
sql
select * from emp
where entrydate > (select entrydate from emp where name = '方东白');
7.2 列子查询(返回一列多行,搭配 IN/ANY/ALL)
表格
| 关键字 | 作用 |
|---|---|
| IN | 匹配集合中任意一个值 |
| ANY/SOME | 满足集合中任意一个条件即可 |
| ALL | 必须满足集合中全部条件 |
示例 1:查询销售部、市场部所有员工(IN)
sql
sql
select * from emp
where dept_id in (select id from dept where name in ('销售部','市场部'));
示例 2:查询工资高于财务部所有人的员工(ALL)
sql
sql
select * from emp
where salary > all(
select salary from emp
where dept_id = (select id from dept where name = '财务部')
);
示例 3:查询工资高于研发部任意一人的员工(ANY)
sql
sql
select * from emp
where salary > any(
select salary from emp
where dept_id = (select id from dept where name = '研发部')
);
7.3 行子查询(返回一行多列,多字段等值匹配)
需求:查询和张无忌薪资、直属领导完全相同的员工
sql
sql
-- 1. 获取张无忌薪资、领导id
select salary, managerid from emp where name = '张无忌';
-- 2. 行子查询匹配双字段
select * from emp
where (salary, managerid) = (select salary, managerid from emp where name = '张无忌');
7.4 表子查询(返回多行多列,当做临时表)
示例 1:查询和鹿杖客、宋远桥职位、薪资完全相同的员工
sql
sql
select * from emp
where (job, salary) in (
select job, salary from emp where name in ('鹿杖客','宋远桥')
);
示例 2:查询 2006-01-01 后入职员工 + 对应部门信息
sql
sql
-- 写法1:标量子查询
select e.*, (select dname from dept d where d.deptno = e.deptno) as dept_name
from emp e
where e.hiredate > '2006-01-01';
-- 写法2:关联JOIN(开发推荐)
select e.*, d.*
from emp e
join dept d on e.deptno = d.deptno
where e.hiredate > '2006-01-01';
八、总结
本文覆盖 MySQL 多表查询全部核心考点:表关系设计、内外连接、自连接、联合查询、四类子查询,所有 SQL 均可直接复制运行。 多表查询核心要点:
- 多表关联必须添加等值条件,避免笛卡尔积;
- 业务优先使用左外连接,保留主表全部数据;
- 自连接必须设置表别名;
- 子查询根据返回结果分为四类,标量子查询最常用;
union all性能优于union,无去重需求优先使用。