关于单表的操作(包括单表的设计、单表的增删改查操作)我们就已经学习完了。接下来我们就要来学习多表的操作,首先来学习多表的设计。
项目开发中,在进行数据库表结构设计时,会根据业务需求及业务模块之间的关系,分析并设计表结构,由于业务之间相互关联,所以各个表结构之间也存在着各种联系,基本上分为三种:
- 一对多(多对一)
- 多对多
- 一对一
2.1 一对多
2.1.1 表设计
需求:根据页面原型及需求文档 ,完成部门及员工的表结构设计
显然,部门和员工就是一种一对多的关系,因为一个部门下是会有多个员工的,但是一个员工只能归属于一个部门,其实这种关系,就是一对多的关系。在这种关系当中,由于一个部门对应多个员工,所以我们把部门叫一的一方,把员工叫多的一方。所以,在一对多的关系当中,我们把一的一方叫做父表,把多的一方叫子表,因为一个父亲可以有多个儿子。因为一个父亲可以有多个儿子,但是一个儿子只能有一个父亲。
提问:这种一对多的关系,在数据库表结构层面该怎么去体现呢?
回答:我们只需要在数据库表结构多的一方来增加一个外键 / 字段关联一的一方的主键即可。
-
员工管理页面原型:(前面已完成tb_emp表结构设计)
部门管理页面原型:
经过上述分析,现已明确的部门表结构:
- 业务字段 : 部门名称
- 基础字段 : id(主键)、创建时间、修改时间
部门表 - SQL语句:
# 建议:创建新的数据库(多表设计存放在新数据库下)
create database db03;
use db03;
-- 部门表
create table tb_dept
(
id int unsigned primary key auto_increment comment '主键ID',
name varchar(10) not null unique comment '部门名称',
create_time datetime not null comment '创建时间',
update_time datetime not null comment '修改时间'
) comment '部门表';
部门表创建好之后,我们还需要再修改下员工表。为什么要修改员工表呢?是因为我们之前设计员工表(单表)的时候,并没有考虑员工的归属部门。
员工表:添加归属部门字段
-- 员工表
create table tb_emp
(
id int unsigned auto_increment comment 'ID'
primary key,
username varchar(20) not null comment '用户名',
password varchar(32) default '123456' null comment '密码',
name varchar(10) not null comment '姓名',
gender tinyint unsigned not null comment '性别, 说明: 1 男, 2 女',
image varchar(300) null comment '图像',
job tinyint unsigned null comment '职位, 说明: 1 班主任,2 讲师, 3 学工主管, 4 教研主管',
entrydate date null comment '入职时间',
dept_id int unsigned comment '归属的部门ID', -- 员工的归属部门,关联部门的id即可
create_time datetime not null comment '创建时间',
update_time datetime not null comment '修改时间',
constraint username
unique (username)
) comment '员工表';
员工表 - 部门表之间的关系:
一对多关系实现:在数据库表中多的一方,添加字段,来关联属于一这方的主键。
2.1.2 外键约束
问题
-
表结构创建完毕后,我们看到两张表的数据分别为:
现在员工表中有五个员工都归属于1号部门(学工部),当删除了1号部门后,数据变为:
1号部门被删除了,但是依然还有5个员工是属于1号部门的。 此时:就出现数据的不完整、不一致了。
问题分析
目前上述的两张表(员工表、部门表),在数据库层面,并未建立关联,所以是无法保证数据的一致性和完整性的。
问题解决
想解决上述的问题呢,我们就可以通过数据库中的"外键约束"来解决。
- 外键约束:让两张表的数据建立连接,保证数据的一致性和完整性。
- 对应的关键字:foreign key
外键约束的语法:
外键约束的添加语法有两种,第一种就是在创建表结构的时候指定,第二种就是表结构创建完了我来添加一个外键。
如果是在创建表的时候添加外键,我们只需要在所有字段都罗列完成之后,后面加上这么一个约束,指定关键字constraint,指定外键约束的名称,然后指定外键的关键字foreign key,在小括号里面来指定哪个字段是外键,对于我们当前来说dept_id这个字段就是一个外键字段,然后再通过references来指定它所关联的主表的字段,这个主表指的就是我们刚才所介绍的父表,这是在创建表的时候指定。
如果表结构创建完了,我们可以通过alter table来指定表名,我们要修改哪一张表,add constraint然后指定外键名称,这就代表我们要添加一个约束,约束的名称是这个, 然后通过foreign key来只当这是一个外键约束,外键字段名是哪一个,再通过references来指定关联的主表的字段名。
-- 创建表时指定
create table 表名(
字段名 数据类型,
...
[constraint] [外键名称] foreign key (外键字段名) references 主表 (主表列名)
);
-- 建完表后,添加外键
alter table 表名 add constraint 外键名称 foreign key(外键字段名) references 主表(主表列名);
当然如果我们在项目开发阶段,如果我们要额外添加外键,就不需要再去记这些SQL语句了,直接图形化界面的方式就可以了。
方式2:图形化界面操作
指定当前表的哪个字段关联目标表的哪个字段
当我们添加外键约束时,我们得保证当前数据库表中的数据是完整的。 所以,我们需要将之前删除掉的数据再添加回来。
当我们添加了外键之后,再删除ID为1的部门,就会发现,此时数据库报错了,不允许删除。
外键约束(foreign key):保证了数据的完整性和一致性。
外键约束添加成功之后,emp表的dept_id字段这里有一个蓝色的小钥匙,这个代表的就是外键约束。
外键约束
当前,我们通过foreign key这种关键字所定义出来的这种外键我们叫物理外键。
物理外键和逻辑外键
-
物理外键
-
概念:使用foreign key定义外键关联另外一张表。
-
缺点:
-
影响增、删、改的效率(需要检查外键关系)。
-
仅用于单节点数据库,不适用与分布式、集群场景。
-
容易引发数据库的死锁问题,消耗性能。
-
-
-
逻辑外键
-
概念:在业务层逻辑中,解决外键关联。
-
通过逻辑外键,就可以很方便的解决上述问题。
-
在现在的企业开发中,很少会使用物理外键,都是使用逻辑外键。 甚至在一些数据库开发规范中,会明确指出禁止使用物理外键 foreign key
2.2 一对一
一对一关系比较典型的就是用户与身份证的关系,一个用户对应一张身份证,而一张身份证也只能归属于 / 关联一个用户。
一对一关系表在实际开发中应用起来比较简单,通常是用来做单表的拆分,也就是将一张大表拆分成两张小表,将大表中的一些基础字段放在一张表当中,将其他的字段放在另外一张表当中,以此来提高数据的操作效率。
一对一的应用场景: 用户表(基本信息+身份信息)
-
基本信息:用户的ID、姓名、性别、手机号、学历
-
身份信息:民族、生日、身份证号、身份证签发机关,身份证的有效期(开始时间、结束时间)
- 如果在业务系统当中,对用户的基本信息查询频率特别的高,但是对于用户的身份信息查询频率很低,此时出于提高查询效率的考虑,我就可以将这张大表拆分成两张小表,第一张表存放的是用户的基本信息,而第二张表存放的就是用户的身份信息。他们两者之间一对一的关系,一个用户只能对应一个身份证,而一个身份证也只能关联一个用户。
那么在数据库层面怎么去体现上述两者之间是一对一的关系呢?
其实一对一我们可以看成一种特殊的一对多。一对多我们是怎么设计表关系的?是不是在多的一方添加外键。同样我们也可以通过外键来体现一对一之间的关系,我们只需要在任意一方来添加一个外键就可以了,这个外键来关联另一方的主键。
为了保证它们之间是绝对的一对一的关系,可以为该外键字段再加上一个unique唯一约束,此时就会保证这一列的值是不会重复的,这样也就保证了一个用户不能对应两个身份证。
一对一 :在任意一方加入外键,关联另外一方的主键,并且设置外键为唯一的(UNIQUE)
SQL脚本:
2.3 多对多
多对多的关系再开发中也是比较常见的,比如说像老师与学生的关系,一个学生可以有多个代课老师,一个代课老师也可以有多个学生。再比如,学生与课程的关系,一个学生可以选修多门课程,而一门课程也可以供多个学生选修。
案例:学生与课程的关系
-
关系:一个学生可以选修多门课程,一门课程也可以供多个学生选择
思考:在数据库表结构当中,如何来体现这两张表之间的多对多的关系?
-
实现关系:建立第三张中间表,中间表至少包含两个外键,分别关联两方主键
-
studentid用来关联的是学生表的主键,而courseid关联的是课程表的主键
-
中间表当中的这些数据就维护了学生与课程之间的关系
-- 学生表
create table tb_student(
id int auto_increment primary key comment '主键ID',
name varchar(10) comment '姓名',
no varchar(10) comment '学号'
) comment '学生表';
-- 学生表测试数据
insert into tb_student(name, no) values ('黛绮丝', '2000100101'),('谢逊', '2000100102'),('殷天正', '2000100103'),('韦一笑', '2000100104');
-- 课程表
create table tb_course(
id int auto_increment primary key comment '主键ID',
name varchar(10) comment '课程名称'
) comment '课程表';
-- 课程表测试数据
insert into tb_course (name) values ('Java'), ('PHP'), ('MySQL') , ('Hadoop');
-- 学生课程表(中间表)
create table tb_student_course(
id int auto_increment comment '主键' primary key,
student_id int not null comment '学生ID',
course_id int not null comment '课程ID',
-- 分别用来关联学生表的主键和课程表的主键
constraint fk_courseid foreign key (course_id) references tb_course (id),
constraint fk_studentid foreign key (student_id) references tb_student (id)
)comment '学生课程中间表';
-- 学生课程表测试数据
insert into tb_student_course(student_id, course_id) values (1,1),(1,2),(1,3),(2,2),(2,3),(3,4);