数据库设计中一对一、多对一、多对多关系依据外键的实现条件及方法

作者:二歪求知iSk2y
链接:https://www.jianshu.com/p/2b27c7ba0653
来源:简书

下面以departments和staff_info表为例(为staff_info添加指向departments的外键)

一个表的字段作为外键的条件:

列值必须非空且唯一测试例子如下:
mysql> create table departments (dep_id int(4),dep_name varchar(11));
Query OK, 0 rows affected (0.02 sec)

mysql> desc departments;
+----------+-------------+------+-----+---------+-------+
| Field    | Type        | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| dep_id   | int(4)      | YES  |     | NULL    |       |
| dep_name | varchar(11) | YES  |     | NULL    |       |
+----------+-------------+------+-----+---------+-------+
rows in set (0.00 sec)

# 创建外键不成功
mysql> create table staff_info (s_id int,name varchar(20),dep_id int,foreign key(dep_id) references departments(dep_id));
ERROR 1215 (HY000): Cannot add foreign key 

# 设置dep_id非空,仍然不能成功创建外键
mysql> alter table departments modify dep_id int(4) not null;
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> desc departments;
+----------+-------------+------+-----+---------+-------+
| Field    | Type        | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| dep_id   | int(4)      | NO   |     | NULL    |       |
| dep_name | varchar(11) | YES  |     | NULL    |       |
+----------+-------------+------+-----+---------+-------+
rows in set (0.00 sec)

mysql> create table staff_info (s_id int,name varchar(20),dep_id int,foreign key(dep_id) references departments(dep_id));
ERROR 1215 (HY000): Cannot add foreign key constraint

# 当设置字段为unique唯一字段时,设置该字段为外键成功
mysql> alter table departments modify dep_id int(4) unique;
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> desc departments;                                                               +----------+-------------+------+-----+---------+-------+
| Field    | Type        | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| dep_id   | int(4)      | YES  | UNI | NULL    |       |
| dep_name | varchar(11) | YES  |     | NULL    |       |
+----------+-------------+------+-----+---------+-------+
rows in set (0.01 sec)

mysql> create table staff_info (s_id int,name varchar(20),dep_id int,foreign key(dep_id) references departments(dep_id));
Query OK, 0 rows affected (0.02 sec)

特别注意:需要主外结合的两个表必须是使用同样的一个引擎类型:两个表必须都是innodb存储引擎

添加外键的方法

一般有两种方法,在创建表的时候添加,或者后期再添加

创建时添加

mysql> create table score(
    -> sid int not null auto_increment primary key,
    -> student_id int,
    -> corse_id int,
    -> number int not null,
    -> constraint fk_sid foreign key (student_id) references student(sid),
    -> constraint fk_corse_id foreign key (corse_id) references course(cid));
 
-----------------------------------------------------------------------------------
[CONSTRAINT symbol] FOREIGN KEY [id] (从表的字段1)

REFERENCES tbl_name (主表的字段2)

[ON DELETE {RESTRICT | CASCADE | SET NULL | NO ACTION}]

[ON UPDATE {RESTRICT | CASCADE | SET NULL | NO ACTION}

CONSTRAINT symbol:可以给这个外键约束起一个名字,有了名字,以后找到它就很方便了。如果不加此参数的话,系统会自动分配一个名字。

FOREIGN KEY:将从表中的字段1作为外键的字段。

REFERENCES:映射到主表的字段2。

ON DELETE后面的四个参数:代表的是当删除主表的记录时,所做的约定。

数据库设计中一对一、多对一、多对多关系依据外键的实现条件及方法

创建后添加

ALTER TABLE employee ADD FOREIGN KEY(dept_id) REFERENCES department(id);

ALTER TABLE employee:在从表employee中进行操作;

ADD FOREIGN KEY(dept_id):将从表的字段dept_id添加为外键;

REFERENCES department(id):映射到主表department当中为id的字段。

删除外键

删除外键时如果你不知道外键名字 就先获取外键名字

show create table emp \G;

删除外键

alter table emp drop foreign key 外键名;

下面的才是重点

关系的实现

在实体关系模型中,我们知道有三种关系:一对一、一对多、多对多。这只是概念上的关系,但是在真实的关系数据库中,我们只有外键,并没有这三种关系,那么我们就来说一说在关系数据库管理系统中,怎么实现这三种关系。

一对多

这里先讲解一对多,因为这个关系最简单。一对多和多对一是一回事,所以就不再提多对一这个词。一对多的概念是一个对象A会对应多个对象B,而从B的角度看,一个对象B只会对于一个对象A。比如说班级和学生就是一对多关系。一个班级对应多个学生,一个学生只会对于一个班级。

? 一对多的关系之所以说简单,是因为RDBMS的【外键】其实就是表示一对多关系。对于一对多关系,我们只需要在“多”的这个表中建立“一”的外键关联即可,而“一”这边的表不需要做任何修改。比如前面说到的班级学生关系。班级表不变,学生表增加班级Id作为外键。

多对多

多对多的关系在数据库设计时比一对一要常见,所以这里先说说多对多。多对多是一个对象A对应多个对象B,从B角度看,一个对象B也会对应多个对象A。比如说学生和课程的关系就是多对多关系。一个学生会学习多门课程,一门课程会有多个学生来选修。

? 在RDBMS中,必须使用中间表来表示多对多的关系。中间表我们可以分成两种,一种是纯粹表示关系的中间表,一种是表示中间实体的中间表。

? 纯粹表示关系的中间表很简单,只需要两列:AID和BID,AID以外键关联到A表的主键,BID以外键关联到B表的主键,然后这两个列组成联合主键。这个中间表纯粹是表示多对多关系而存在,在业务上不会有对应的实体与之对应。比如前面提到的学生和课程的关系,如果我们只需要知道哪些学生上哪些课,哪些课有哪些学生选,不需要有更多的信息的情况下,我们就可以建立“学生课程”中间表,里面只有学生ID和课程ID两个字段。

? 中间实体是在纯粹的中间关系表的基础上,加上了更多的属性,从而形成了一个新的实体。比如前面提到的学生和课程的关系,如果我们需要记录学生选课的时间、学生选择这门课程后的考试成绩,那么我们就像建立一个“选课”实体,该实体具有如下属性:

数据库设计中一对一、多对一、多对多关系依据外键的实现条件及方法

? 这就是一个中间实体,已经完全脱离了普通的多对多关系中间表,而变成一个实体的形式的存在,所以按照前面博客中讲到的主键设计的原则,我们可以单独建立一个选课ID的列作为数据库的主键,该主键本身并没有业务含义。

一对一

一对一概念上是说一个对象A最多对应一个对象B,从B角度看,也是一个对象B最多对应一个对象A。比如说班主任(教师)和班级的关系,一个班主任最多管理一个班级,一个班级也最多只有一个班主任。

? 一对一的关系在数据库设计中,是使用的最少的关系,因为一般来说,如果两个实体是一对多关系,那么我们也可以把这两个实体合并成一个实体。但是在设计中,我们仍然会遇到两个完全不同的实体,之间存在一对一关系。

? 一对一的RDBMS实现是在其中的一个表上建立外键指向另一个表,同时在该外键列上建立唯一约束。比如前面说到的班主任和班级关系,我们可以在班级表建立班主任字段,然后再在该字段建立唯一约束。因为每个班都会有班主任,所以班主任字段是不允许为空的。一个教师可以当某个班的班主任,也可以不当任和班的班主任,同时也不可能在班级表的班主任字段上出现两次,所以最多就当一个班的班主任,所以该设计满足需求。

? 那么我们可不可以反过来,在教师表中建立所管理的班级Id字段,指向班级表,并建立唯一约束呢?除了不满足“每个班必然有一个班主任”这个业务约束外,其他都没有问题。所以如果对于一对一的情况,如果那边必须要求持有另一边,则就在哪边增加外键字段;如果没有要求必须持有一个另一类实体的话,就哪边添加外键列都行。


/**学生表*/
CREATE TABLE student (
stu_id INT AUTO_INCREMENT,
NAME VARCHAR(30),
age INT ,
class VARCHAR(50),
address VARCHAR(100),
PRIMARY KEY(stu_id)
)
 
/*学生课程表*/
CREATE TABLE Course(
cour_id INT AUTO_INCREMENT,
NAME VARCHAR(50),
CODE VARCHAR(30),
PRIMARY KEY(cour_id)
)
 
/**学生课程关联表*/
CREATE TABLE Stu_Cour(
sc_id INT AUTO_INCREMENT,
stu_id INT ,
cour_id INT,
PRIMARY KEY(sc_id)
)

/*添加外键约束*/
ALTER TABLE Stu_Cour ADD CONSTRAINT stu_FK1 FOREIGN KEY(stu_id) REFERENCES student(stu_id)
ALTER TABLE Stu_Cour ADD CONSTRAINT cour_FK2 FOREIGN KEY(cour_id) REFERENCES Course(cour_id)

相关推荐