前言
说起三大范式,我脑海里第一反应就是这几句话:
- 当实体 A 与实体 B 之间的关系是一对一时,会把实体 A 的主键作为 实体 B 的外键。当然关系是一对一时,反过来也可以,即把 实体B 的主键当做实体 A 的外键;
- 当实体 A 与实体 B 之间的关系是一对多时,会把 A 的主键当做实体 B 的外键;
- 当实体 A 与实体 B 之间的关系是多对多时,通常会考虑建一张新表来保存这两个实体之间的关系。
但是有时候实体与实体之间的关系并不是非常容易的看出来,所以靠这些理解并不足以设计出符合规范的表结构。在读了相应的资料后,发现自己对三大范式的理解是非常浅薄的、肤浅的,所以就写了这篇博客来记录自己对三大范式重新认识的过程。
什么是范式(NF)?
在理解三大范式之前,需要明白什么是范式(NF)?
按照教材中的定义,范式是:
符合某一种级别的关系模式的集合,表示一个关系内部各属性之间的联系的合理化程度。
可以理解为:一张数据表的表结构所符合的某种设计标准的级别。
三大范式定义
以下是三大范式的定义:
- 第一范式:符合
1NF
的关系中的每个属性都不可再分。 - 第二范式:
2NF
在1NF
的基础之上,消除了非主属性对于码的部分函数依赖。 - 第三范式:
3NF
在2NF
的基础之上,消除了非主属性对于码的传递函数依赖。
说实话,我一开始看到第二范式和第三范式的定义时,感到非常迷惑:
- 什么是非主属性?
- 什么是码?
- 范式的定义为什么和函数有关?
但是在理解了这些概念之后,才意识到意识到这几条定义是如此的精确。
在理解“非主属性”和“码”之前,需要先彻底搞明白什么是“函数依赖”,因为“非主属性”和“码”的概念是建立在“函数依赖”的概念之上的。
什么是函数依赖?
若在一张表中,在属性(或属性组)X的值确定的情况下,必定能确定属性Y的值,那么就可以说Y函数依赖于X,写作 X → Y。
光看概念可能不太好理解什么是函数依赖,还是需要通过一个例子来理解。
学生表如下:
学号 | 姓名 | 性别 | 年龄 |
---|---|---|---|
1420233906 | 罗辑 | 男 | 21 |
1420233907 | 云天明 | 男 | 22 |
1420233908 | 章北海 | 男 | 23 |
1420233909 | 程心 | 女 | 20 |
这张学生表有四个属性:学号、姓名、性别、年龄。我们知道一个学号对应一个学生,假如你的班主任问你 1420233906 是谁,你就可以告诉他 1420233906 是罗辑,除此之外,你还能告诉你的班主任,罗辑是男的,21 岁。
换句话说:
- 在学号确定的情况下,就必定能确定姓名的值,即姓名函数依赖于学号,写作姓名 → 学号;
- 在学号确定的情况下,就必定能确定性别的值,即性别函数依赖于学号,写作性别 → 学号;
- 在学号确定的情况下,就必定能确定年龄的值,即年龄函数依赖于学号,写作年龄 → 学号;
还能在学生表中找到其他的函数依赖关系吗?答案是否定的。
如果学生表中,只有这四条记录,好像是可以通过姓名来确定学号、性别、年龄的值。但是在大多数班中肯定存在同名同姓的学生,例如张伟:
学号 | 姓名 | 性别 | 年龄 |
---|---|---|---|
1420233906 | 罗辑 | 男 | 21 |
1420233907 | 云天明 | 男 | 22 |
1420233908 | 章北海 | 男 | 23 |
1420233909 | 程心 | 女 | 20 |
1420233909 | 张伟 | 男 | 21 |
1420233910 | 张伟 | 男 | 22 |
虽然同叫张伟,但却是两个不同的学生。当数学老师叫张伟时,两个张伟可能同时站起来。即在姓名确定的情况下,无法确定学号的值,无法确定性别的值也无法确定年龄的值。同理:
- 在性别确认的情况下,无法确定其他任意一个属性的值;
- 在年龄确认的情况下,无法确定其他任意一个属性的值;
再来看一个更难的例子:
学号 | 姓名 | 系名 | 系主任 | 课名 | 分数 |
---|---|---|---|---|---|
1420233906 | 罗辑 | 机电工程系 | 艾AA | 高等数学 | 99 |
1420233907 | 罗辑 | 机电工程系 | 艾AA | 大学英语 | 98 |
1420233908 | 罗辑 | 机电工程系 | 艾AA | 大学物理 | 97 |
1420233909 | 程心 | 经济系 | 维德 | 高等数学 | 88 |
1420233909 | 程心 | 经济系 | 维德 | 毛概 | 88 |
1420233910 | 程心 | 经济系 | 维德 | 大学英语 | 88 |
在这张表中存在哪些函数依赖关系呢?
- 学号 → 姓名
- 学号 → 系名(一个学生只属于一个系)
- 系名 → 系主任(一个系只有一个系主任)
- 学号 → 系主任(一个学生只属于一个系、一个系只有一个系主任,即在学号确定的情况下,必定可以确认该学生所属系的系主任)
- (学号,课名) → 分数
但是以下函数依赖关系则不成立:
- 学号 → 课名
- 学号 → 分数
- 课名 → 分数
- ...
从“函数依赖”这个概念展开,还会有三个概念:
1)完全函数依赖
在一张表中,若 X → Y,且对于 X 的任何一个真子集(假如属性组 X 包含超过一个属性的话),X ' → Y 不成立,那么我们称 Y 对于 X 完全函数依赖,记作 X F→ Y
例如:(学号,课名) F→ 分数,即分数完全函数依赖于(学号,课名)属性组。
- 属性 Y 要完全函数依赖于属性(组)X 的前提是:属性 Y 要函数依赖于属性(组)X,分数函数依赖于(学号,课名),第一条满足;
- (学号,课名)是一个属性组,超过一个属性,它的真子集有{学号}、{课名},无法只通过学号来确定分数,也无法只通过课名来确定分数,故满足对于(学号、课名)的任何一个真子集,X' → Y 不成立。
所以分数完全函数依赖于(学号,课名)属性组成立。
2)部分函数依赖
假如 Y 函数依赖于 X,但同时 Y 并不完全函数依赖于 X,那么我们就称 Y 部分函数依赖于 X,记作 X P→ Y
例如:(学号,课名) P→ 姓名,即姓名部分函数依赖于(学号,课名)组。
- 首先姓名函数依赖于(学号,课名)属性组成立;
- (学号,课名)的真子集为{学号}、{课名},在这些真子集中,可以通过学号来确定姓名,即姓名函数依赖于学号,故姓名不完全函数依赖于(学号,课名)属性组,即姓名部分函数依赖于(学号,课名)。
3)传递函数依赖
假如 Y 不包含于 X,且 X 不函数依赖于 Y,Z 函数依赖于 Y,且 Y 函数依赖于 X,那么我们就称 Z 传递函数依赖于 X ,记作 X T→ Z。
什么是码?
设 K 为某表中的一个属性或属性组,若除 K 之外的所有属性都完全函数依赖于 K(这个“完全”不要漏了),那么我们称 K 为候选码,简称为码。
什么是非主属性
主属性之外的属性就是非主属性。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。