2 数据模型
2.1 层次数据模型
2.1.1 基础概念
说明:在现实世界中,有很多数据都是具有层次的特征的,由数据结构我们自然想到可以用树
来表达现实世界,层次数据模型就是基于这种想法提出来的。
层次数据模型的提出,首先是为了模拟这种按层次组织起来的事物。
在下面,我们会提到几个概念:记录型,字段,记录。我们用一张图来清楚地表示:
在这个表格中,黄色部分表示
记录的型
;蓝色部分表示记录
。而记录的型里面某个属性叫做字段
。
2.1.2 结构
说明:层次数据模型中有着PCR关系,这里的PCR并非高中生物课本的PCR,而是指双亲子女关系(Parent-Chlid-Relationship,PCR)
,他是层次模型中最基本的数据关系,他代表了两个记录型之间一对多的关系。例如学生和课程,一个学生可以选修多个课程。
也就是说,我们的层次数据模型就是基于PCR的一种模型,其应该为一颗倒立的树,除了根,每个记录型都应该有他自己对应且唯一的父母,但是每个父母可以有多个子女。
其实抛开这个问题不讲,在现实世界中,不是说所有的关系都是一对多的,也有可能是多对一,或者多对多,这就是所谓的多元关系
。假设我们有这么一个例子,一个学生可以选择多门课,一门课可以被多个学生选择。假设学生是父,那么课就是子,多门课只能对应一个父,也就是说想要一门课要想能被多个学生选择,那么根据PCR关系,他必须复制多份一模一样的课出来,如图所示:
用PCR处理后:
这样的话导致了数据冗余,原本一个人工智能的课在数据库中拷贝了三份。
2.1.3 虚拟记录
说明:由上面简单的例子可以知道PCR会导致数据冗余,不但浪费存储空间,还导致数据不一致。由此我们引出了虚(拟)记录
的概念。
如果遇见上述的关系,我们无须复制一份,只需要在引用的地方用其指针来代替即可,用指针替代的记录叫做虚记录,虚记录通常用下标V表示,指针用虚线箭头表示。
举一个例子来说:
由图可知,学生指向课程,但是指过去无法指回来;所以我们指向他的虚记录,虚记录指回本体;课程本体如果指向学生,那学生无法指回课程;所以课程本体指向学生的虚记录,学生虚记录指向学生本体。
2.2 网状数据模型
2.2.1 基本概念
说明:与层次数据模型类似,在网状数据模型中,也是以记录
作为数据的存储单位。但是不同的是,在网状数据模型中,字段
这个名词变成了数据项
,并且在层次数据模型中,字段是最小单位不可再分的,可是数据项是可以再分的,我们把可以再分的数据项叫做多值数据项
,如例子所示:
学生名 | 学号 | 班级 | 年龄 | 生日 | 地址 |
---|---|---|---|---|---|
baka爱 | 15 | 9年一班 | 15 | 2000.1.25 | {(酒店),(饭店)} |
在地址这个数据项中,其对应了一个多值集合。这也侧面说明了多值数据项可以用有序的集合
表示,简单的多值数据项我们也叫做向量
。
作为记录,其在数据结构中还有对应的地址;如果想要查找某一条记录,只需要查对应地址即可,每个记录有一个唯一地标识他的数据库码(DataBase Key,DBK)
。DBK可以看成记录的逻辑地址,可做记录的替身或用来寻找记录。
那么如何表达两团数据之间的联系呢?比如两团数据一个是班级的数据一个是学生的数据,那么两团数据的联系由什么表示呢?这就要提到系(set)
的概念了。
假如在班和学生两团数据中,一个班对应多个学生,那么我们叫把班级-学生
的系叫做它们之间的联系。在这里例子中,班为1而学生为n,则我们把1的一方叫做首记录
,把N的一方叫做属记录
。
需要注意的是,在一个系的属记录中,有时会包含多种类型的属记录,例如班级-学生这个系中,学生可以再分,分为男学生和女学生,那么此时我们把拥有多种类型属记录的系,叫做多属系
。
或者再举一个例子:一个银行账户可以作为首记录,而属记录是这个账户的一笔笔的账,这些账可以是存款的、提款的或转账的,他们分属于不同的记录型。
提示:实际上,多属性可以拆分为多个系,比如班级-学生
这个系可以拆分为班级-男学生
和班级-女学生
,但是分开多个系你要来回在不同的系找人不方便,所以对于多属系来说,其优点是查找方便。
2.2.2 联系记录
说明:在层次数据模型中,我们有说到虚记录
,在网状数据模型中,我们不需要考虑虚记录,因为他并没有PCR关系,就拿下面这个例子来说:
班和学生构成一个系,运动队和学生构成一个系,学生既是“班-学生”的属记录,也是“运动队-学生”的属记录,无须用PCR复制,无须用指针创建虚记录。
结果上面的例子我们可以总结出以下的道理:
- 一个记录型可以作为几个系的首记录,也可以作为几个系的属记录。
- 一个记录型可以作为一个系的首记录,也可以作为另一个系的属记录。
- 但是!一个记录型不能做一个系的首记录,又做这个系的属记录。
第一小点和第二小点可以用一个例子:“班级-学生”,“家庭-学生”和“学生-选课”这三个系中,学生作为中转站,他可以做多个系的属纪录,也可以做另外多个系的主记录。
第三小点也不难理解:在“班级-学生”这个系中,你不能说学生又是属记录又是主记录。
上面的叙述中,第三点显然不符合现实。为什么这么说?举个例子:
职员和领导,职员是领导的下属,但是领导也是职员,也就是说在领导也在职员记录型里面,这显然是不符合要求的。但是现有的知识中没能解决这个问题,所以网状数据模型引入了联系记录(link 记录)
这个概念。
联系记录干了这么一件事情:拿职员和领导的关系来说,我们中间引入一个link变量,来接收职员的箭头,再把箭头指向领导。
上面这样表示的意思是:一个领导下管理多个职员,但是职员中也有领导。这样看可能看不出什么,但是这样看呢?
在上面,我们谈论的班级-学生,其对应的是一团班级(可以理解为班级表)和一团学生(可以理解为学生表),所以实际上我们说的系是系型
,而具体的某个学生,某个班级,就是系值
,也就是我们说的记录,只是体现在系的层面上换了个名字。需要注意的是,一个记录值不能出现在同一类型的多个系值当中,打个比方就是学生表里面不能有两条一模一样的记录。
联系记录的另外一个用法是:由于一个记录值不能出现同一系型的多个系值中,所以比如说学生-课程
这个系,课程里面有一门数据结构,它同时被两个人选,但是它的记录不能出现在这个系型里面两次,所以可以用联系记录做如下表示:
从看到上图我们可以知道,为了避免课4被两个学生用同一个记录,所以我们将每个课和link挂钩,用两个link来当课4的中转站。这样相当于是层次数据模型中的多元关系。
2.2.3 结构
说明:谈完上面的知识,我们又要说说网状数据模型的结构。在层次结构中我们用树
来表示其结构,但是在网状数据模型中我们用的是链表
来实现,如图所示:
在这里要提到一个,链表用指针串联也是有讲究的,我们一般分为三类:前向指针
,后向指针
和首记录指针
,其中前向指针是必备
的。
指针 | 说明 |
---|---|
前向指针 | 就是依次从根开始,依次指向“下”一个记录 |
后向指针 | 和前向指针相反 |
首记录指针 | 属记录指向首记录 |
2.2.4 单值系和无首系
说明:实际上,两个名词指的是同一个东西,这种系通常没有首记录,所以叫无首系
,或者具体点说,一个单位里面有很多部门,但是单位只有一个,我们可以直接省略,所以无首系又叫做单值系
。
再比如“班级-学生”,班级有很多个,我们不能说“班级-学生”是单值系,但是“学校-班级”中的学校只有一个,总而言之,单值系和无首系都是相对的,要视具体问题来看。
2.3 关系数据模型
实际上,上面两个数据模型我们在杂谈(一)提到过都是旧时代的产物了,基本上没什么人用了,目前主流的数据模型是这一小节要提到了关系数据模型
。
关系数据模型是以集合论中的关系概念
为基础发展起来的数据模型,他的基本数据结构是表
,我们一般叫做关系
。我们把现实世界的实体和实体之间的联系用表来表示。
在这里要先说明,实际上,关系并不能完全地等同于表,严格意义上来说要说等于一张加了特殊限制的二维表
。因为在关系数据模型中,默认其遵循第一范式
,即不允许表中套表。关于范式问题我们在后面的小节中会提到。当然,有特殊需要时,我们可以选用不遵守第一范式
的扩充关系数据模型
。
2.3.1 基本概念
在关系数据模型中,以往模型的术语在这里变得大为不同。下面用一张图来说明:
黄色荧光笔部分叫做属性
,具体的某个属性叫做属性名
,在不混淆的情况下我们可以简称属性
。蓝色荧光笔部分叫做元组
,元组可以看做是网状数据模型中我们说的记录
。属性名对应的取值范围叫做属性域
,属性中有n个属性名我们就叫n目
,上面的图表中,这是一个五目的关系
。整个表我们叫做关系
,用R或r(R)表示,从这些概念来看,关系是n目元组的集合
。一般来说我们描述关系是用R(属性1,属性2,...,属性n)
来描述的,具体到这里的例子:学生表(Sno,Sname,Ssex,Dno,Dname)
。
既然说到关系是集合,就势必要要说到高中数学中集合的概念,集合具有无序性,确定性,互异性三大特性,这也说明了01元组放在第一行和放在第二行是一样的(无序性),互异性说明元组是不能重复的,确定性说明元组一定要是确定的,不能模棱两可。
还有,从上表我们可以看到,表中存在NULL值(空值)
,虽然名字这么念,可实际上并不是一个值,它表示该地方未知或不存在,具体到这里的例子来看,该学生很可能已经入学但是还没分配属于哪个系,所以该学生不知道哪个系的。
不同属性是可以有相同的域的,比如出生年份和生日,他们的域都是有限集合的时间,但是虽然域相同,但是他们所指的意思不一样。
提示:空值会给数据库访问和更新带来很多困难,所以应该尽量避免使用空值。
2.3.2 键 (码)
造成这一小节的标题原因来源于英文,因为英文是key,翻译过来就是键或码。在这一小节中,很多名词在不同的书上叫法都不同。
2.3.2.1 候选键
说明:候选键(候选码)也叫键,通俗来讲他是某个属性
或属性组(多个属性)
,通过这个属性我们可以唯一地确定某一个元组
。候选键实际上是狭义的键,广义的键应该指所有键种类的集合。
拿上面这张表来说,候选键应该是Sno。有人说Sname呢?抱歉,名字可能重名,但是学号不可能重复。
其中候选键所包含的属性叫做主属性
,不包含的叫做非主属性
。对应到上表,Sno是主属性,其余属性皆为非主属性。
提示:关于候选键有两者须知。
其一:候选键在关系中至少有一个,这对应到集合概念中的确定性
,如果你连候选键都没有,说明你连某个元组都确定不了,那这不就是模棱两可吗。
其二:候选键里不可以包含候选键,比如说(Sno,Sname)是候选键吗?不是,因为这个属性集里面包含了候选键。
2.3.2.2 超键
说明:在上面的例子中,我们曾经提到过(Sno,Sname),当时我们说到它不是候选键,但是能唯一确定一条元组,我们把这种属性集叫做超键
。
2.3.2.3 主键
说明:拥有一个或多个候选键的时候,在里面挑一个作为主键,其他作为候补键
(这里不是候选哦,是候补哦,看好字),主键有些书叫做主码
,关键值
,关键码
。
2.3.2.4 外键
说明:用这个r1里的某个键,可以引用(寻找)其他关系中的某条元组,也就是可以引用其他表的主键,那我们叫这个r1上的某个键为外键
。有些书叫做外码
或外值
,而关系r1也叫做外码依赖的参照关系
,另外的那张表被叫做被参照关系
。
在下一小节中,我们会提到参照完整性约束
,参照完整性约束要求参照关系中的任意元组在特定属性上的取值必然等于被参照关系中某个元组在特定属性上的取值。
提示:外键实际上是一个逻辑指针,也就是r1的某个键通过其他关系的指针去寻找对应的元组。所以指针必须非空。
2.4 约束
在数据库系统中,为了维持数据库中数据的一致性,使其能够准确地描述显示世界,所以要采用一定的约束,这里我们讲的约束是针对关系数据模型
的,在后面我们会讲到更多的约束。
2.4.1 实体完整性约束
前面我们说过,允许关系里面出现空值,但是主键一定不能为空,每个关系都应该有一个主键,每个元组的主键的值都是唯一地,主键的值不能为NULL,否则无从区别和识别元组。
2.4.2 域完整性约束
简而言之,你的属性值必须得是域中的值。比如说,学号是int类型的,你把它的值填了个char的,一定会报错。
2.4.3 参照完整性约束
参照完整性约束要求参照关系中的任意元组在特定属性上的取值必然等于被参照关系中某个元组在特定属性上的取值。参照的完整性要求关系中不允许引用不存在的实体。与实体完整性是关系模型必须满足的完整性约束条件,目的是保证数据的一致性。参照完整性又称引用完整性
。举个简单的例子,你说学生表中的宿舍区对应宿舍表中的宿舍区,那么你学生表中的宿舍区有个C栋,宿舍表中的宿舍区一定要有C栋,不能说没有。
2.4.5 用户定义的完整性约束
说明:这是针对某一具体数据的约束条件,应该由应用环境决定。比如年龄不可能是负的,性别不可能不男不女不人妖。一般来说常见的用户定义的完整性约束如下:
- NULL:代表空值,但是他不是一个值,不等于0,而是代表这里没有值。在以前我们是二值逻辑,即要么
true
要么false
,引入NULL后,我们由二值逻辑变为三值逻辑。 - NOT NULL:不允许对应的属性值为空。
- UNIQUE:不允许属性值重复,即列值不能重复。
DEFAULT:如果属性值为空,则填个
默认值
上去。一般默认值有三种情况:- 第一种是事先定义好的字符,比如如果为空就补个0上去。
- 第二种是置为空格字符串,把NULL变成长度为0的字符串或者变成某一个不可能出现的值。
- 第三种就是变成NULL,如果你要这么做,那你 前面不能再加一个NOT NULL不然语义矛盾。
- CHECK:利用他,可以说明属性值的范围,也就是加限制条件,比如人的年龄不可能为负数,你可以加age>0。