文章目录
-
- 什么是范式?
- 第一范式(1NF)
- 第二范式(2NF)
-
-
- 函数依赖
- 完全函数依赖
- 部分函数依赖
- 传递函数依赖
- 超关键字/超键/超码/码
- 候选码/候选键
- 主键
- 主属性
- 非主属性
-
- 第三范式(3NF)
- BC范式(BCNF)
- 总结
今天刚好回忆下范式,参考这篇 如何理解关系型数据库的常见设计范式? 文章,我在此文章进一步改进。
什么是范式?
教材书这样定义:符合某一种级别的关系模式的集合,表示一个关系内部各属性之间的联系的合理化程度。
实话实说,真不懂!!然后看了博主的文章解释的很好。也就可以理解为:一张数据表的表结构所符合的某种设计标准的级别。就比如王者荣耀的VIP,有VIP1,VIP2,VIP3等等,VPI2是基于VIP1而来的,而不是直接跳过VIP1直到VIP2,所以VIP1的奖励也可以领取。那么数据库范式也分为1NF,2NF,3NF,4NF,5NF。还有一种是BCNF,对于3NF进一步改进。
一般而已,对于数据库的设计,满足第三范式或者BC范式就足够了。
第一范式(1NF)
第一范式是对关系模型的最基本的要求,其定义就是数据库表的每一列都是不可再分割的。
举正例可能看不清楚,举个反例:
如上图,学生、系、课程这些列还可以再分,所以上表不满足第一范式,只要不满足第一范式的表都不是关系型数据库表。
正确的做法是:(顺便插入几条记录)
仅仅满足第一范式还是存在很多问题,比如数据冗余过大,插入异常,删除问题,修改冗余的问题。
- 数据冗余过大:一张表中,每个学生的学号,姓名,还有系名,系主任重复出现。
- 插入异常:假设新建一个系,那么这个系的信息在还没学生前不能提前录入表中,因为没有主键。
注1:根据三种关系完整性约束中实体完整性的要求,关系中的码(注2)所包含的任意一个属性都不能为空,所有属性的组合也不能重复。为了满足此要求,图中的表,只能将学号与课名的组合作为码,否则就无法唯一地区分每一条记录。
注2:码:关系中的某个属性或者某几个属性的组合,用于区分每个元组(可以把“元组”理解为一张表中的每条记录,也就是每一行)。
- 删除问题:假设某个系的学生都没有了,删除后那么该系的信息也都没有了。实际上,虽然没有学生了,但不代表这个系不存在。
- 修改冗余:假设某个学生转系,那么就得修改他的系信息,而且还得修改多次,比如上面的小山转系,那么得修改2次。
所以为了解决这些问题,出现了第二范式。
注:属性是否真的不可拆分,根据你的设计目标而定,比如说姓名,是否还得继续拆为性和名呢?看自己。
第二范式(2NF)
要了解第二范式,需要一些前置知识:
函数依赖
定义:若在一张表中,在属性(或属性组)X 的值确定的情况下,必定能确定 属性Y 的值,那么就可以说:Y函数依赖于X,记作 X -> Y。
比如上面的表,通过学号就可以确定姓名(一条记录),那么说明:姓名 依赖于 学号;而通过姓名不一定能确定学号(多条记录),因为可能有同名,说明:学号 不依赖于 姓名。
大白话:知道某属性值就可以唯一确定另一属性的值。
完全函数依赖
定义:如果非主属性B函数依赖于构成某个候选关键字的一组主属性A,而且A的任何一个真子集不能被B函数依赖,则称B完全函数依赖于A。如果已经确定就一个主属性,那么可以肯定其他属性完全依赖于该主属性。
比如上面的表,通过学号和课程名就可以确定成绩(仅学号或者课程名不能查出成绩),所以成绩完全函数依赖于学号和课程名。
大白话:通过一个属性值,能唯一确定某值,如果是属性组,那么该属性组中必须所有属性组合在一起才能确定某属性值。
部分函数依赖
定义跟上面相反了,定义:若B函数能依赖于A的真子集,则称B部分函数依赖于A。
比如上面的表,通过学号和课程名可以确定姓名,但是单单通过学号也可以确定姓名,所以姓名部分依赖于学号和课程名。
大白话:仅对于属性组来说,如果属性组中不需要所有属性组合在一起就能确定某值。
传递函数依赖
定义:在 Y 不包含于 Z 且 Y 不函数依赖于 X,假设 Z函数依赖于Y,且Y函数依赖于X,那么就称Z传递函数依赖X。
比如上面的表,通过学号可以查出系名,通过系名可以查出系主任,所以通过学号可以查出系主任,所以称系主任传递函数依赖于学号。
超关键字/超键/超码/码
定义:能够唯一标识一条记录的属性或属性集。
比如上面的表,通过学号可以查出系名,通过学号和姓名也可以查出系名类似这样的,可以说包含该学号的任意属性组合都是超键。
候选码/候选键
定于:若关系中的一个属性或属性组的值能够唯一地标识一个元组(就是一条记录),且他的真子集不能唯一的标识一个元组(完全函数依赖),则称这个属性或属性组做候选码。或者说:能够唯一标识一条记录的最小属性组。
比如上面的表,可以通过学号确定某个属性的值,但是对于成绩,仅仅使用学号不够,还得有课程名才可能获取成绩,其实对于课程名单单使用学号也无法确定。所以对于 学号 和 课程名 这个属性组就是候选码。而通过学号和姓名可以确定系名,但是单单通过学号也可以确定,而姓名是多余的,所以姓名不是候选码。
主键
当有多个候选码时,可以从候选码中选取一个/或一组作为主键。
比如上面的的表,要唯一确定一条记录,那么得知道 学号 和 课程名,而这两个都得作为主键。
主属性
在候选码中挑选,比如候选码为:(A,B),(A,C)。那么主属性为A,B,C。
非主属性
包含在任何一个候选码中的属性成为主属性。那么非主属性也就知道了。
比如上面的表,已经确定 学号 和 课程名 这个属性组就是候选码,那么 学号 和 课程名 都是主属性,那么其他属性就都是非主属性。
那么第二范式的定义是:在满足第一范式的前提下,是否存在非主属性对于候选码的部分函数依赖或者说对于所有的非主属性是完全函数依赖于候选码。若存在,则数据表最高只符合1NF的要求,若不存在,则符合2NF的要求。(大白话:找主键,能够确定某列的值,但是如果有多个主键,看看这些主键还要不要拆分,判断条件:这些主键能够确定某列A的值并且不存在这些主键中其中一个就能确定某列A值)
判断的方法是:
- 第一步:找出数据表中所有的候选码。
- 第二步:根据第一步所得到的码,找出所有的主属性。
- 第三步:数据表中,除去所有的主属性,剩下的就都是非主属性了。
- 第四步:查看是否存在非主属性对候选码的部分函数依赖。
例子:
- 第一步:
- 查看所有每一单个属性,当它的值确定了,是否剩下的所有属性值都能确定。
- 查看所有包含有两个属性的属性组,当它的值确定了,是否剩下的所有属性值都能确定。
- ……
- 查看所有包含了六个属性,也就是所有属性的属性组,当它的值确定了,是否剩下的所有属性值都能确定。
- 有一个诀窍(参考的答主说的):如果A是候选码,那么所有包含A的属性组就不是码了,因为码要求除码之外的所有属性都完全函数依赖于码。但是上面的表,通过学号可以查询姓名,也可以通过学号和系名查询姓名或者学号和系主任查询姓名,那么很明显,系名或者系主任并不是码,而学号是码,这符合答主说的。但是单单使用学号,不能查出课程名和成绩,所以除了学号作为码,还得需要课程名作为码,通过学号和课程名属性组作为码才可以查出成绩。所以我认为答主的说法太绝对了,应该这样说:如果A是码,并且包含A的属性组都可以查询某属性值,那么说明该包含A的属性组不是码,但是如果包含A的属性组能够确定单单A查询不到的某列值,那么说明包含A的属性组也是码。(不知道这样理解对不对…)
- 第二步:第一步已经分析出候选码:学号、课程名。那么它们都是主属性。
- 第三步:知道了主属性,那么剩下的都是非主属性:姓名,系名,系主任,成绩。
- 第四步:
- (学号,课程名)-> 姓名,但 学号 -> 姓名,所以存在非主属性对于候选码的部分函数依赖。
- (学号,课程名)-> 系名,但 学号 -> 系名,所以存在非主属性对于候选码的部分函数依赖。
- (学号,课程名)-> 系主任,但 学号 -> 系主任,所以存在非主属性对于候选码的部分函数依赖。
- (学号,课程名)-> 成绩,非主属性对于候选码的完全函数依赖。
所以上表不满足第二范式。那么需要对表进行拆分,根据上面的分析,可以拆分为:
- 成绩表(学号,课程名,成绩)
- 学生表(学号,姓名,系名,系主任)
现在重新分析这样是否满足第二范式:
- 成绩表:学号和课程名是候选键,也就是主属性,成绩是非主属性。单单通过学号或者课程名都查不出成绩,所以可以发现不存在非主属性对于候选码的部分函数依赖,满足第二范式。
- 学生表:学号是候选键,也是主属性,姓名、系名、系主任是非主属性。因为候选码只有一个,所以可以确定不存在非主属性对于候选码的部分函数依赖,满足第二范式。
现在来看看第一范式存在的问题是否解决:
-
数据冗余过大:有所改进,但是比如还有多个计算机系的学生,那么系名和系主任两个列还是重复出现。
-
插入异常:假设新建一个系,那么这个系的信息在还没学生前不能提前录入表中,因为没有主键。还存在该问题。
-
删除问题:假设某个系的学生都没有了,删除后那么该系的信息也都没有了。实际上,虽然没有学生了,但不代表这个系不存在。还存在该问题。
-
修改冗余:假设某个学生转系,那么就得修改他的系信息,而且还得修改多次,比如上面的小山转系,那么得修改2次。已经解决,因为可以保证学生表中每位学生只出现一次,所以转系时只需要改一次。
所以在第二范式的基础上,提出了第三范式来解决这些问题。
第三范式(3NF)
定义:在第二范式的基础上,消除了非主属性对于候选码的传递函数依赖。也就是说:要求数据库表中不包含其他表的非关键字信息(消除冗余的列,或者说非主键外的所有字段必须互不依赖)。
分析上面的俩张表:
- 成绩表:主属性为学号和课程名,非主属性为成绩,不存在非主属性对候选码的传递函数依赖,满足第三范式。
- 学生表:主属性为学号,非主属性为姓名、系名和系主任。通过学号可以查出系主任,通过系名可以查出系主任,存在非主属性对候选码的传递函数依赖,不满足第三范式。
改进学生表:把学校信息和系信息拆分成两张表:
- 系表(系名,系主任)
- 学生表(学号,姓名,系名)
不分析了,很容易看得出满足第三范式。
现在来看看之前的问题是否解决:
-
数据冗余过大:已经解决。每位学生肯定还得对于一个系名,但至少系主任不会重复出现。
-
插入异常:假设新建一个系,可以直接录入系表。已经解决。
-
删除问题:假设某个系的学生都没有了,删除后,该系的信息还可以存在,除非真要删除该系信息。已经解决。
BC范式(BCNF)
定义:在满足第三范式的基础上,消除主属性对于候选码的部分与传递函数依赖。(大白话:在多个主属性中,消除多余主属性)
是对于第三范式的改进,上面的第三范式都没问题的,为什么还得改进?来看看特殊情况:
- 某公司有若干个仓库;
- 每个仓库只能有一名管理员,一名管理员只能在一个仓库中工作;
- 一个仓库中可以存放多种物品,一种物品也可以存放在不同的仓库中。每种物品在每个仓库中都有对应的数量。
那么关系模式 仓库(仓库名,管理员,物品名,数量) 属于哪一级范式?分析一波:
- 已知函数依赖集:仓库名 -> 管理员,管理员 -> 仓库名,(仓库名,物品名)-> 数量
- 候选码:仓库名、管理员、物品名
- 非主属性:数量
可以发现满足第一范式,并且不存在非主属性部分函数依赖和传递函数依赖于候选码,所以满足第三范式。
虽然满足第三范式,但看看有什么问题存在:
- 假设要为一个仓库换管理员,那么在上表中可能得修改多次。出现数据冗余。
- 如果新增一个仓库,但尚未放入物品,是否可以指派管理员?不可以,因为物品名也是主属性,得遵守实体完整性约束。
- 如果一个仓库被清空后,需要删除所有与该仓库有关的物品存放记录,那么仓库本身和管理员也会随着删除。
所有虽然满足了第三范式,但该表不是一个很好,主要的原因在于:存在着主属性对于候选码的部分函数依赖与传递函数依赖。也就是:仓库名 对于 管理员、物品名 存在部分函数依赖与传递函数依赖:
- 部分函数依赖:通过管理员和物品名可以确定仓库名,通过管理员可以确定仓库名 或者 通过仓库名和物品名可以确定管理员,通过仓库名可以确定管理员。
- 传递函数依赖:仓库名和物品名可以确定数量,而仓库名可以确定管理员,那么管理员和物品名也可以确定数量。(多余主属性)
如何解决?拆表:
- 仓库表(仓库名,管理员)
- 存库表(仓库名,物品名,数量)
可以发现上面存在的问题可以解决。
总结
第一范式:数据库表中的属性(或者字段或者列)不可再分割。
第二范式:在满足第一范式的前提下,找主键,能够确定某列的值,但是如果有多个主键,看看这些主键还要不要拆分,判断条件:这些主键能够确定某列A的值并且不存在这些主键中其中一个就能确定某列A值
第三范式:在满足第二范式的前提下,消除冗余的列,或者说非主键外的所有字段必须互不依赖。
BC范式:在满足第三范式的前提下,在多个主属性中,消除多余主属性。
最好理解范式中出现的问题。并且范式并不是需要绝对遵守的准则,就像姓名这个字段,可能不需要继续拆分,可能需要拆为性和名两个字段。
有错请指出