数据库中范式的概念

之前对范式了解了不深,好好整理一下。

1NF

1NF 的定义为:符合 1NF 的关系中的每个属性都不可再分。

1NF 是所有关系型数据库的基本要求。如果数据库的设计不满足这个要求,操作一定不能成功(为什么不能成功?)

但是如果只符合 1NF 的设计的话,仍然可能出现数据冗余过大、插入异常、删除异常、修改异常的问题,以下表为例:

学号 姓名 系名 系主任 课名 分数
101 小明 经济 王强 数学 95
101 小明 经济 王强 英语 87
101 小明 经济 王强 化学 88
102 小红 经济 王强 数学 89
102 小红 经济 王强 英语 90
102 小红 经济 王强 计算机 91
103 小芳 法律 刘玲 数学 92
103 小芳 法律 刘玲 法学 93

对于上表有下列问题:

每一名学生的学号、姓名、系名、系主任这些数据重复多次。每个系与对应的系主任的数据也重复多次——数据冗余过大

假如学校新建了一个系,但是暂时还没有招收任何学生(比如3月份就新建了,但要等到8月份才招生),那么是无法将系名与系主任的数据单独地添加到数据表中去的 (注1)——插入异常

注1:根据三种关系完整性约束中实体完整性的要求,关系中的码(注2)所包含的任意一个属性都不能为空,所有属性的组合也不能重复。为了满足此要求,图中的表,只能将学号与课名的组合作为码,否则就无法唯一地区分每一条记录。

注2:码:关系中的某个属性或者某几个属性的组合,用于区分每个元组(可以把“元组”理解为一张表中的每条记录,也就是每一行)

假如将某个系中所有学生相关的记录都删除,那么所有系与系主任的数据也就随之消失了(一个系所有学生都没有了,并不表示这个系就没有了)。——删除异常

假如李小明转系到法律系,那么为了保证数据库中数据的一致性,需要修改三条记录中系与系主任的数据。——修改异常

正因为仅符合1NF的数据库设计存在着这样那样的问题,我们需要提高设计标准,去掉导致上述四种问题的因素,使其符合更高一级的范式(2NF),这就是所谓的“规范化”。

2NF

定义:若 $R \in 1NF$,且每一个非主属性完全函数依赖于任何一个候选码。则 $R\in2NF$。

总的来说,2NF 在 1NF 的基础上,消除了非主属性对于码的部分函数依赖。这涉及到四个概念——“函数依赖”、“”、“非主属性”、“部分函数依赖”。

函数依赖

定义:设 R(U) 是属性集 U 上的关系模式,X,Y 是 U 的子集。若对于 R(U) 的任意一个可能的关系 r,r 中不可能存在两个元组在 X 上的属性值相等,而在 Y 上的属性值不等,则称 X 函数确定 YY 函数依赖于 X,记作 $X \rightarrow Y$。

可以这样简要理解:若在一张表中,在属性(或属性组)X 的值确定的情况下,必定能确定属性 Y 的值,那么就可以说 Y 函数依赖于 X,写作 $X\rightarrow Y$。也就是说,在数据表中,不存在任意两条记录,它们在 X 属性(或属性组)上的值相同,而在 Y 属性上的值不同。这也是“函数依赖”名字的由来,类似于函数关系 y=f(x),在 x 的值确定的情况下,y 的值一定是确定的。

我们把上面那张表拖下来:

学号 姓名 系名 系主任 课名 分数
101 小明 经济 王强 数学 95
101 小明 经济 王强 英语 87
101 小明 经济 王强 化学 88
102 小红 经济 王强 数学 89
102 小红 经济 王强 英语 90
102 小红 经济 王强 计算机 91
103 小芳 法律 刘玲 数学 92
103 小芳 法律 刘玲 法学 93

对于上表中的数据,找不到任何一条记录,它们的学号相同而对应的姓名不同。所以我们可以说姓名函数依赖于学号,写作学号$\rightarrow$姓名。但是反过来,因为可能出现同名的学生,所以有可能不同的两条学生记录,它们在姓名上的值相同,但对应的学号不同,所以我们不能说学号函数依赖于姓名。表中的其他函数依赖还有如:

  • 系名$\rightarrow$系主任
  • 学号$\rightarrow$系主任
  • (学号,课名)$\rightarrow$分数

但是以下函数依赖关系不成立:

  • 学号$\rightarrow$课名
  • 学号$\rightarrow$分数
  • 课名$\rightarrow$系主任
  • (学号,课名)$\rightarrow$姓名

从“函数依赖”这个概念展开,还有三个概念:

完全函数依赖

定义:在 R(U) 中,如果 $X\rightarrow Y$,并且对于 X 的任何一个真子集 $X’$,都有 $X’\nrightarrow Y$,则称 Y 对 X 完全函数依赖,记作 $X\stackrel{F}{\rightarrow}Y$

例如:

  • $学号\stackrel{F}{\longrightarrow}姓名$
  • $(学号,课名)\stackrel{F}{\longrightarrow}分数$ (注:因为同一个学号对应的分数不确定,同一个课名对应的分数也不确定)

部分函数依赖

定义:若 $X\rightarrow Y$,但 Y 不完全函数依赖于 X,则称 Y 对 X 部分函数依赖(partial functional dependency),记作$X\stackrel{P}{\rightarrow}Y$。

例如:

  • $(学号,课名)\stackrel{P}{\longrightarrow}姓名$,也就是说姓名函数依赖于学号和课名,但是姓名不满足函数依赖的这个条件:对于 X 的任何一个真子集 $X’$,都有 $X’\nrightarrow Y$。因为对于 X,有学号确定姓名,因此只满足部分函数依赖的关系。

传递函数依赖

定义:在 R(U) 中,如果 $X\rightarrow Y(Y\nsubseteq X)$,$Y\nrightarrow X$,$Y\rightarrow Z$,$Z\nsubseteq Y$,则称 Z 对 X 传递函数依赖(transitive functional dependency)。记作 $X\stackrel{传递}{\longrightarrow}Y$

用人话描述就是:加入 Z 函数依赖于 Y,且 Y 函数依赖于 X,并且 Y 不包含于 X,且 X 不函数依赖于 Y,那么称 Z 传递函数依赖于 X,记作 $X\stackrel{T}{\rightarrow}Z$

定义:设 K 为 中的属性或属性组合,若 $K\stackrel{F}{\rightarrow}U$,则 K 为 R 的候选码(candidate key)。

人话:设 K 为某表中的一个属性或属性组,若除 K 之外的所有属性都完全函数依赖于 K,那么我们称 K 为候选码,简称为。实际可以理解为:假如 K 确定的情况下,该表除 K 之外的所有属性的值也就随之确定,那么 K 就是码。一张表中可以有超过一个码。实际通常选择其中一个码作为主码

例如:

  • 对于上面的表,(学号,课名)这个属性组就是码,该表中有且仅有这一个码(假设所有课没有重名的情况)。

非主属性

定义:包含在任何一个候选码中的属性称为主属性(primary attribute);不包含在任何候选码中的属性称为非主属性(nonprime attribute)。

例如:

  • 对于上面的表,因为码是(学号,课名),因此主属性就是学号与课名。

接下来我们就可以回来看 2NF 了。首先我们需要判断,上表是否符合 2NF 的要求?根据 2NF 的定义,判断的依据实际上就是看数据表中是否存在非主属性对于码的部分函数依赖。若存在,则数据表最高只符合1NF的要求,若不存在,则符合2NF的要求。判断的方法是:

  • 第一步:找出数据表中所有的
  • 第二步:根据第一步所得到的码,找出所有的主属性
  • 第三步:数据表中,除去所有的主属性,剩下的就是非主属性了。
  • 第四步:查看是否存在非主属性对码的部分函数依赖

为了避免频繁拖动,我再把这张表拉下来:

学号 姓名 系名 系主任 课名 分数
101 小明 经济 王强 数学 95
101 小明 经济 王强 英语 87
101 小明 经济 王强 化学 88
102 小红 经济 王强 数学 89
102 小红 经济 王强 英语 90
102 小红 经济 王强 计算机 91
103 小芳 法律 刘玲 数学 92
103 小芳 法律 刘玲 法学 93

下图表示了表中所有的函数依赖关系:

按照上面的步骤,我们可以分析到如下的结果:

  • 第一步,码是(学号,课名)
  • 第二步,主属性是:学号课名
  • 第三步,非主属性有:姓名系名系主任分数
  • 第四步:
    • 对于(学号,课名)$\rightarrow$姓名,有学号$\rightarrow$姓名,存在非主属性姓名对码(学号,课名)的部分函数依赖。
    • 对于(学号,课名)$\rightarrow$系名,有学号$\rightarrow$系名,存在非主属性系名对码(学号,课名)的部分函数依赖。
    • 对于(学号,课名)$\rightarrow$系主任,有学号$\rightarrow$系主任,存在非主属性系主任对码(学号,课名)的部分函数依赖。

因此上表存在非主属性对于码的部分函数依赖,最高只符合 1NF 的要求,不符合 2NF 的要求。

为了让上表符合 2NF 的要求,我们必须消除这些部分函数依赖,只有一个办法,就是将大数据表拆分成两个或更多个更小的数据表。在拆分的过程中,要达到更高一级范式的要求,这个过程叫做“模式分解”。模式分解不是唯一的,以下是其中一种方法:

选课表(学号,课名,分数)

学生表(学号,姓名,系名,系主任)

我们接下来判断选课表与学生表是否符合 2NF 的要求:

  • 对于选课表,其码是(学号,课名),主属性是学号课名,非主属性是分数
    • 学号确定,不能唯一确定分数课名确定也不能唯一确定分数。所以不存在非主属性分数对码(学号,姓名)的部分函数依赖,所以此表符合 2NF 的要求。
  • 对于学生表,其码是学号,主属性是学号,非主属性是姓名系名系主任。因为码只有一个属性,所以不可能存在非主属性对于码的部分函数依赖,所以此表符合 2NF 的要求。

因此我们可以看出,只有在码为属性组的时候才会出现非主属性对码的部分函数依赖。

下图表示模式分解后新的函数依赖关系:

下表表示模式分解后的新数据:

选课表:

学号 课名 分数
101 数学 95
101 英语 87
101 化学 76
102 数学 72
102 英语 98
102 计算机 88
103 数学 82
103 法学 82

学生表:

学号 姓名 系名 系主任
101 小明 经济 王强
102 小红 经济 王强
103 小芳 法律 刘玲

现在来看一下进行同样的操作是否还存在着之前的问题?

  1. 小明转系到法律系

    只需要修改一次小明对应的系的值即可。——有改进

  2. 数据的冗余是否减少了?

    学生的姓名、系名与系主任不再像之前一样重复那么多次了。——有改进

  3. 删除某个系中所有的学生记录

    该系的信息仍然全部丢失。——无改进

  4. 插入一个尚无学生的新系的信息。

    因为学生表的码是学号,不能为空,所以此操作不被允许。——无改进

所以说,仅仅符合 2NF 的要求,很多情况下还是不够的,而出现问题的原因,在于仍然存在非主属性系主任对于码学号的传递函数依赖。为了能进一步解决这些问题,我们还需要将符合 2NF 要求的数据表改进为符合 3NF 的要求。

3NF

定义:
设关系模式 ,若 R 中不存在这样的码 X,属性组 Y 及非主属性 成立,,则称

人话:第三范式(3NF)在 2NF 的基础上,消除了非主属性对于码的传递函数依赖。也就是说,如果存在非主属性对于码的传递函数依赖,则不符合 3NF 的要求。

接下来我们看看上表中的设计是否符合 3NF 的要求。

对于选课表,主码为(学号,课名),主属性为学号课名,非主属性只有分数,不可能存在传递函数依赖,所以选课表的设计符合 3NF 的要求。

对于学生表,主码为学号,主属性为学号,非主属性为姓名系名系主任。因为学号$\rightarrow$系名,同时系名$\rightarrow$系主任,所以存在非主属性系主任对于码学号的传递函数依赖。所以学生表的设计不符合 3NF 的要求。

为了让数据表设计达到 3NF,我们必须进一步进行模式分解为以下形式:

选课表(学号,课名,分数)

学生表(学号,姓名,系名)

系表(系名,系主任)

选课表符合 3NF 的要求,上面已经分析过了。

对于学生表,码为学号,主属性为学号,非主属性为系名,不可能存在非主属性对于码的传递函数依赖,所以符合 3NF 的要求。

对于表,码为系名,主属性为系名,非主属性为系主任,不可能存在非主属性对于码的传递函数依赖(至少需要三个属性才可能存在传递函数依赖关系),所以符合 3NF 的要求。

新的函数依赖关系如下:

新的数据表如下:

选课表

学号 课名 分数
101 数学 95
101 英语 87
101 化学 76
102 数学 72
102 英语 98
102 计算机 88
103 数学 82
103 法学 82

学生表

学号 姓名 系名
101 小明 经济
102 小红 经济
103 小芳 法律

系表

系名 系主任
经济 王强
法律 刘玲

接下来我们继续分析进行同样的操作是否还存在之前的问题?

  1. 删除某个系中所有学生的记录

    该系的信息不会丢失。——有改进

  2. 插入一个尚无学生的新系信息:

    因为系表与学生表目前是独立的两张表,所以不影响。——有改进

  3. 数据冗余更少了。——有改进

由此可见,符合 3NF 要求的数据库设计,基本上解决了数据冗余过大、插入异常、修改异常、删除异常等问题。

但是 3NF 会不会有什么问题呢?

首先我们看这样一个问题:

  1. 某公司有若干个仓库;
  2. 每个仓库只能有一名管理员,一名管理员只能在一个仓库中工作;
  3. 一个仓库中可以存放多种物品,一种物品也可以存放在不同的仓库中。每种物品在每个仓库中都有对应的数量。

那么关系模式仓库(仓库名,管理员,物品名,数量)属于哪一级范式?

答:

已知函数依赖集:仓库名$\rightarrow$管理员,管理员$\rightarrow$仓库名,(仓库名,物品名)$\rightarrow$数量

码:(管理员,物品名),(仓库名,物品名)

主属性:仓库名、管理员、物品名

非主属性:数量

不存在非主属性对码的部分函数依赖和传递函数依赖

∴ 此关系模式属于 3NF。

基于此关系的关系可能如下表所示:

仓库名 管理员 物品名 数量
上海仓 张三 iPhone 5s 30
上海仓 张三 iPad Air 40
北京仓 李四 iPhone 5s 50
北京仓 李四 iPad Mini 60

数据依赖关系如下:

虽然上关系模式已经属于 3NF,那么这个关系模式是否存在问题呢?我们来看下面的操作:

  1. 先新增加一个仓库,但尚未存放任何物品,是否可以为该仓库指派管理员?

    ——不可以,因为物品名也是主属性,根据实体完整性的要求,主属性不能为空,插入异常

  2. 某仓库被清空后,需要删除所有与这个仓库相关的物品存放记录,会带来什么问题?

    ——仓库本身与管理员的信息也被随之删除了,删除异常

  3. 如果某仓库更换了管理员,会带来什么问题?

    ——这个仓库有几条物品存放记录,就要修改多少次管理员信息,修改异常

从这里我们可以得出结论,在某些特殊情况下,即使关系模式符合 3NF 的要求,仍然存在着插入异常,修改异常与删除异常的问题,仍然不是 ”好“ 的设计。

造成此问题的原因:存在着主属性对于码的部分函数依赖与传递函数依赖。(在此例中就是存在主属性仓库名对于码(管理员,物品名)的部分函数依赖。

BCNF

定义:关系模式 ,若 且 $Y\nsubseteq X$ 时 X 必含有码,则

人话:在 3NF 的基础上,消除了主属性对码的部分与传递函数依赖。

接下来我们看一下上表中的设计是否满足 BCNF。

由上面的图知道,对于(管理员,仓库名)$\rightarrow$物品名,有管理员$\rightarrow$物品名,存在主属性仓库名对于码(管理员,物品名)的部分函数依赖。

解决办法就是要在 3NF 的基础上消除主属性对于码的部分与传递函数依赖。

仓库(仓库名,管理员)

库存(仓库名,物品名,数量)

新的数据表如下:

仓库表:

仓库名 管理员
上海仓 张三
北京仓 李四

库存表:

仓库名 物品名 数量
上海仓 iPhone 5s 30
上海仓 iPad Air 40
北京仓 iPhone 5s 50
北京仓 iPad Mini 60

新的数据依赖关系如下:

我们来看一下继续分析进行同样的操作是否还存在之前的问题?

  1. 先新增加一个仓库,但尚未存放任何物品,是否可以为该仓库指派管理员?

    ——可以,仓库表是独立的。

  2. 某仓库被清空后,需要删除所有与这个仓库相关的物品存放记录,会影响管理员吗?

    ——不会,这时可以单独删除仓库的物品存放记录。

  3. 如果某仓库更换了管理员,会带来什么问题?

    ——只需要单独修改仓库表中的管理员信息,不需要修改很多的表项。

这样,之前的插入异常,修改异常与删除异常的问题就被解决了。

4NF 在 3NF 的基础上去除了多值依赖,未来补充一些样例。

5NF 在 4NF 的基础上消除了连接依赖,这个了解一下就好。

补充信息

上述定义均取自王珊的《数据库系统概论(第五版)》教材

大部分内容引自如何解释关系数据库的第一第二第三范式?

文章作者: 40m41h42t
文章链接: http://qrzbing.cn/2019/06/14/paradigm/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 QRZ's Blog