关系模型
我们已经知道,关系数据库是建立在关系模型上的。而关系模型本质上就是若干个存储数据的二维表,可以把它们看作很多Excel表。
表的每一行称为记录(Record),记录是一个逻辑意义上的数据。
表的每一列称为字段(Column),同一个表的每一行记录都拥有相同的若干字段。
字段定义了数据类型(整型、浮点型、字符串、日期等),以及是否允许为NULL。
注意NULL表示字段数据不存在。一个整型字段如果为NULL不表示它的值为0,同样的,一个字符串型字段为NULL也不表示它的值为空串''。
通常情况下,字段应该避免允许为NULL。不允许为NULL可以简化查询条件,加快查询速度,也利于应用程序读取数据后无需判断是否为NULL。
主键
在关系数据库中,一张表中的每一行数据被称为一条记录。一条记录就是由多个字段组成的。
例如,students表的两行记录:
自增整数类型:数据库会在插入数据时自动为每一条记录分配一个自增整数,这样我们就完全不用担心主键重复,也不用自己预先生成主键;
全局唯一GUID类型:使用一种全局唯一的字符串作为主键,类似8f55d96b-8acc-4636-8cb8-76bf8abc2f57。GUID算法通过网卡MAC地址、时间戳和随机数保证任意计算机在任意时间生成的字符串都是不同的,大部分编程语言都内置了GUID算法,可以自己预算出主键。
联合主键
关系数据库实际上还允许通过多个字段唯一标识记录,即两个或更多的字段都设置为主键,这种主键被称为联合主键。
对于联合主键,允许一列有重复,只要不是所有主键列都重复即可:
如果我们把上述表的id_num和id_type这两列作为联合主键,那么上面的3条记录都是允许的,因为没有两列主键组合起来是相同的。
没有必要的情况下,我们尽量不使用联合主键,因为它给关系表带来了复杂度的上升。
主键是关系表中记录的唯一标识。主键的选取非常重要:主键不要带有业务含义,而应该使用BIGINT自增或者GUID类型。主键也不应该允许NULL。
可以使用多个列作为联合主键,但联合主键并不常用。
外键
当我们用主键唯一标识记录时,我们就可以在students表中确定任意一个学生的记录:
我们还可以在classes表中确定任意一个班级记录:
但是我们如何确定students表的一条记录,例如,id=1的小明,属于哪个班级呢?
由于一个班级可以有多个学生,在关系模型中,这两个表的关系可以称为“一对多”,即一个classes的记录可以对应多个students表的记录。
为了表达这种一对多的关系,我们需要在students表中加入一列class_id,让它的值与classes表的某条记录相对应:
关系就是实体与实体之间的关系,我们将这种关系分为三种:一对一,一对多,多对多。一般我们指的关系是表与表之间关系。
一对一
一张表的一条记录一定只能与另外一张表的一条记录进行对应,反之亦然。
下面用学生表来举例。
学生表:姓名,性别,年龄,身高,体重,婚姻状况,籍贯,家庭住址,紧急联系人。
Id(P) | 姓名 | 性别 | 年龄 | 身高 | 体重 | 婚姻状况 | 籍贯 | 家庭住址 | 紧急联系人 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
表设计成以上这种形式,不是不可以,它确实符合要求。但是其中性别,年龄,体重生属于常用数据,但是婚姻状况,籍贯,住址,联系人属于不常用数据。如果每次查询都是查询所有数据,那么这些不常用数据会影响查询效率,实际这些不常用数据又不用。这种情况,解决方案就是,将常用数据和不常用数据分离,分离成两张表。
常用信息表
Id(P) | 姓名 | 性别 | 年龄 | 身高 | 体重 |
|
|
|
|
|
|
|
|
|
|
|
|
不常用信息表
婚姻状况 | 籍贯 | 家庭住址 | 紧急联系人 |
|
|
|
|
|
|
|
|
如果两张表分成这样,那么问题就来,如果我们需要查询某个人的家庭住址,那么怎么查询呢?也就是两个表之间没有关联关系。解决办法就是,让不常用信息和常用信息一定能够对应得上,找一个具有唯一性的字段来共同连接两张表。通过分析和观察,我们可以拿ID这个字段来将两张表共同连接起来。
所以不常用信息表可以改成这样。
Id(P) | 婚姻状况 | 籍贯 | 家庭住址 | 紧急联系人 |
|
|
|
|
|
|
|
|
|
|
因为id是主键,是唯一的(上面表中P表示主键,以下也是P代表主键),这样两张表就有了关系,在常用表中的一条记录,永远只能在不常用表中匹配一条记录,反过来,在不常用表中一条记录,永远只能在常用表中匹配一条记录,这个关系就是一对一。
一对多(多对一)
定义:一张表中有一条记录可以对应另外一张表中的多条记录,但是反过来,另外一张表的一条记录只能对应第一张表的一条记录,这种关系就是一对多,或者多对一。
下面通过一个国家有多个省份来举例一对多的关系
国家表
ID(P) | 名称 | 全球所属位置 |
|
|
|
省份表
ID(P) | 名称 | 所在国家位置 |
|
|
|
以上关系,一个国家可以在省份表中找到多条记录(也有可能是一条),但是一个省份表永远只能找到一个国家。这就是典型的一对多关系。但是以上设计,解决了实体的设计表问题,但是没有解决关系问题,省份找不出国家,国家找不到省份。
解决方案:在某一张表中增加一个字,能够找到另外一张表的中的记录。应该在省份表中增加一个字段指向国家表,因为省份表的记录只能匹配到一条国家记录。
修改后的省份表如下
ID(P) | 名称 | 所在国家位置 | 国家ID(P) |
|
|
|
|
多对多
定义:一张表(A)中的一条记录能够对应另外一张表(B)中的多条记录,同时B表中的一条记录也能在对应A表中的多条记录,这就是多对多关心。
以下通过教师教学,老师和学生表来设计举例。
老师表
T_Id(P) | 姓名 | 性别 |
|
|
|
学生表
S_Id(P) | 姓名 | 性别 |
|
|
|
以上设计方案实现了实体的设计,但是没有维护实体之间的关系。一个老师教过多个学生,一个学生也被多个老师教过,这种场景以上表就体现不了。
解决方案:不管在哪张表增加字段,都会出现一个问题,该字段要保存多个数据,而且是与气体表有关系的字段,不符合表设计规范,这种情况,我们增加一个中间关系表。
具体设计如下:
老师表
T_Id(P) | 姓名 | 性别 |
|
|
|
学生表
S_Id(P) | 姓名 | 性别 |
|
|
|
中间关系表:
ID | T_Id(老师) | S_ID(学生) |
1 |
|
|
2 |
|
|
增加中间表之后,中间表与老师表形成一对多关系,而且中间关系表是多表。这样就能够唯一找到一表的关系。同样的学生表与中间表也是一对多的关系。
学生找老师的过程:1 找出学生id-> 2中间表寻找匹配记录(多条)-> 3老师表匹配(一条)
老师找学生过程: 1 找出学生id –> 2中间表寻找匹配记录(多条)-> 3学生表匹配(一条)
这样就从一对多,然后到多对一,从而就得到结果就是多对多的效果。这种多对多,在现实项目中最常见的就是电子商务网站上的订单和会员之间的关系。