关系数据库是建立在关系模型上的,而关系模型本质上就是由多个存储数据的二维表组成的。表的每一行叫做记录( Record),记录是一个逻辑意义上的数据。表的每一列称为字段(Column),同一张表中每一列的数据类型都是一致的。字段包含的数据类型包括整形、浮点型、字符串、日期等等,以及是否允许为空(Null)。
关系数据库的表和表之间的逻辑关系有三种:“一对多”、“多对一”和“一对一”。
比如,一个专业的信息可以存在以下的表中:
MajorID | Name | Advisor |
1 | Computer Science | David |
2 | Economics | John |
上表中每一行对应一个专业的信息:专业代号,专业名字,和专业负责人。一个专业中有很多学生,所以专业和学生的关系是“一对多”:
StudentID | Name | MajorID | Gender | Age |
1 | Daniel | 1 | M | 22 |
2 | Kitty | 2 | F | 21 |
3 | William | 1 | M | 22 |
如果我们要通过学生找到他对应的专业具体信息,我们可以定位学生的记录,比如ID为1的Daniel,要确定他专业的负责老师,只需要根据他MajorID的值1找到Major中MajorID是1的记录,然后找到对应的Chair就是David。
如果我们想把Major的信息拆分得更细,我们可以专门创建一个Advisor表格:
AdvisorID | Name | Age |
1 | David | 35 |
2 | John | 40 |
那Major只需要储存AdvisorID就行了:
MajorID | Name | AdvisorID |
1 | Computer Science | 1 |
2 | Economics | 2 |
这样,一个Major就对应一个Advisor,Major和Advisor就是“一对一”关系。
这些表中,各种ID要么是主键(Primary Key)要么就是外键(Foreign Key),下面我们就来学习这些重要的概念。
主键
在关系数据库中,一张表中的每一行数据被称为一条记录。一条记录由多个字段组成的。例如下面的Student表:
StudentID | Name | MajorID | Gender | Age |
1 | Daniel | 1 | M | 22 |
2 | Kitty | 2 | F | 21 |
3 | William | 1 | M | 22 |
每一条记录都包含若干定义好的字段。同一个表中的所有记录都有一致的字段定义。
关系表中有个很重要的约束,就是任意两条记录不能重复。这里的不能重复指的是每条记录需要一个能和其他记录区别开来的特定字段,这个字段称为主键。
假设我们将Name字段作为主键,那么通过Daniel和Kitty就能找到唯一的特定记录。但是如此设定的话,我们就无法存入同名的学生了,因为插入相同主键的记录在关系表中是被禁止的。
对于关键字最关键的一点是:记录一旦被插入表中,主键最好不要修改,因为主键是用来定位记录的,主键的变动必然会造成一定的影响。所以选取主键的基本原则是:不选取任何与业务相关的字段作为主键,比如身份证号、手机号、邮箱地址这些看上去似乎可行的字段,但事实上,这些字段都有一定程度上的变动风险。
最好选择与业务完全无关的字段作为主键,一般这个字段命名为id。常见的可作为id字段的类型有:
- 自增整数类型:数据库会在插入数据后自动为每条记录分配一个自增整数,这样我们就不用担心主键重复,也不用自己预先生成主键。
- 全局唯一GUID类型:这是一种全局唯一的字符串,类似8f55d96b-8acc-4636-8cb8-76bf8abc2f57。GUID算法通过网卡MAC地址、时间戳和随机数生成在任意时间都不同的字符串,大部分编程语言内置了GUID算法,可以自动生成主键。
对于大部分应用来说,通常自增类型的主键就能满足需求。
联合主键
关系数据库也允许表通过多个字段来定位记录,既使用两个或多个字段作为主键,这种主键叫做联合主键。对于联合主键,允许一列有重复,但是所有主键的组合必须是唯一的,比如下面的Enrollment:
StudentId | CourseId | Time |
2 | 4 | 05:00 |
2 | 5 | 05:01 |
3 | 5 | 05:25 |
此表记录了学生加课的信息,StudentId和CourseId共同组成联合主键,也就意味着一名学生可以选择多个课程,一个课程也能同时被多名学生选取,但是学生和课程的组合是唯一的,代表一个学生一旦选取特定课程后,就不能再次选取相同的课程了。主键的设定是通过SQL定义的,在下一章节我们就会学习到。
外键
一对多
当我们使用主键来标识记录时,我们可以在Student表中通过StudentId来确定任意学生的记录:
StudentID | Name | Gender | Age |
1 | Daniel | M | 22 |
2 | Kitty | F | 21 |
3 | William | M | 22 |
我们也可以通过Major表通过MajorId来获取特定专业的信息:
MajorID | Name | Advisor |
1 | Computer Science | David |
2 | Economics | John |
但是我们如何通过Student表来找到特定学生所属的专业呢?理论上一个专业可以有多个学生,这种关系称为“一对多”,既一个Major的记录可以对应多个Student的记录。为了表示这种“一对多”关系,我们需要在Student表中加入一列MajorID,使其与Major表的MajorID相对应:
StudentID | Name | MajorID | Gender | Age |
1 | Daniel | 1 | M | 22 |
2 | Kitty | 2 | F | 21 |
3 | William | 1 | M | 22 |
如此我们就能根据MajorID直接定位出学生所属的专业:比如Daniel的MajorID是1,那么所对应的专业名就是Computer Science;Kitty的MajorID为2,对应Economics。这种通过另一个表的主键联合两张表的字段就称之为外键。要注意的是,外键必须是另一个表格的主键。
多对多
除了“一对多”的关系之外,我们也可以通过外键实现“多对多”的关系。比如一个学生可以选择多门课程,一个课程也可以被多个学生选择,因此学生和课程之间存在“多对多”关系。多对多的关系实际上是由两个“一对多”关系实现的,我们需要一个中间表格,来关联两个一对多关系,比如下面两个表格Student和Course之间存在着“多对多”关系:
StudentId | Name | Age |
1 | Daniel | 22 |
2 | Kitty | 21 |
3 | William | 22 |
CourseId | CourseName |
1 | CS101 |
2 | EE203 |
3 | EC403 |
若要将Student和Course关联起来,我们需要创建一个中间表Enrollment将每个学生所对应的课程记录下来:
EnrollmentId | StudentId | CourseId |
1 | 2 | 1 |
2 | 2 | 2 |
3 | 3 | 2 |
通过这个中间表,我们就能得知学生和课程的关系:StudentId为2的Kitty选择了CourseId为1的CS101,同理Kitty还选择了EE203,然后William也选择了EE203。通过此表我们也可以了解课程和学生的关系,比如CS101只有一个学生Kitty,而EE203有两个学生:Kitty和Wlliam。因此,若想实现两种表的“多对多”关系,创建一张中间表即可。
一对一
“一对一”关系指的是,一个表的一条记录对应另一个表的唯一个记录。比如我们的Student可以多加一栏PhoneNumberId,然后这个PhoneNumberId对应的是PhoneNumber表的记录:
StudentId | Name | Age | PhoneNumberId |
1 | Daniel | 22 | 1 |
2 | Kitty | 21 | 2 |
3 | William | 22 | 3 |
PhoneNumberId | PhoneNumber |
1 | 134… |
2 | 135… |
3 | 139… |
通过这两张表,我们能得到每个学生对应的手机号:Daniel的手机号是134…,Kitty的是135…,William的是139…。当然了,我们也可以直接将手机号存入Student表中。但是假如我们只想知道学生号码有哪些的话,将PhoneNumber单独拆分出来对于数据的抓取更方便,这样也加快了查询速度。
以上就是数据模型的介绍,关系型数据库中的数据需要用表格来存储,表的每一行叫记录(Record),表的每一列称为字段(Column)。每张表格中的单独记录需要一个唯一的外键来定位记录,而外键是辅助一个表指向另一个表的字段,外键必须是其他表的主键。表格之间的关系有三种:一对一,一对多,多对多。在多对多的关系中,我们需要创建一个中间表来连接互相关的两张表。