[数据库之八] 数据库范式与设计

1、函数依赖理论

    关系模式中各属性之间互相依赖、互相制约的联系称为数据依赖。

(1)定义和分类

严谨的数学定义

若对实例中的所有元组对 t1 和 t2,若 t1[α] = t2[α],则 t1[β] = t2[β],则称满足函数依赖 α → β。


【解析】一个元组就是数据表中的一条数据,若在一张表中,在属性(或属性组)α 的值确定的情况下,必定能确定属性 β 的值,那么就可以说 β 函数依赖于α,写作 α β。比如对于学生表,学号可以推出姓名,则称姓名函数依赖于学号,写作 学号 姓名,但是反过来就不行了,因为不同学生可能重名。


分类:

  • 平凡/非平凡函数依赖

    严谨的定义:

如果 X 决定 Y,而且 Y 属于 X,也就是 Y 是 X 的真子集,则称该依赖是平凡依赖;否则为非平凡依赖

【实例解析】

    AB → A,A 属于 AB,这个依赖属于平凡依赖。

平凡函数依赖:在学生表(学生,姓名,年级)中,(学号,姓名)可以推出学号和姓名其中的任意一个,(学号,姓名) 学号 是平凡函数依赖。

非平凡函数依赖:在学生表(学生,姓名,年级)中,(学号,姓名)可以推出学生所在的年级,但年级不是(学号,姓名)的子集,(学号,姓名) 年级 是非平凡函数依赖。


  • 部分/完全函数依赖

    严谨的定义:

如果 X → Y,且对于 X 的任意真子集 X' 均不具有 X' → Yz,则称 Y 对 X 完全函数依赖,否则称 Y 对 X 部分函数依赖。

【实例解析】

完全函数依赖:通过 { 学号, 选修课程号 } 可以得到 { 该生本门选修课程的成绩 },而通过单独的 { 学号 } 或单独的 { 选修课程号 } 都无法得到该成绩,则说明 { 该生本门选修课程的成绩 } 完全依赖于 { 学号, 选修课程号 }

部分函数依赖:通过 { 学号, 课程号 } 可以得到 { 该生姓名 },而通过单独的 { 学号 } 已经能够得到 { 该生姓名 },则说明 { 该生姓名 } 部分依赖于 { 学号, 课程号 }


  • 传递函数依赖

    严谨的定义:

若 A → B,B → C,则 A → C,这个过程称为传递依赖。

【实例解析】

    在关系 **R(学号,宿舍,费用)**中,通过 **{ 学号 }** 可以得到 **{ 宿舍 }**,通过 **{ 宿舍 }** 可以得到 **{ 费用 }**,反之则不成立,则存在传递依赖 **{ 学号 } $\rightarrow$ { 费用 }**。



(2)函数依赖集的闭包

定义:

给定关系模式 r(R),如果 r(R) 的每一个满足 F 的实例也满足 f,则 R 上的函数依赖 f 被 r 上的函数依赖集 F 逻辑蕴涵。

【实例解析】

    给定关系模式 r ( A, B, C, G, H, I ) 及函数依赖集:

    A → B
    A → C
    CG → H
    CG → I
    B → H

那么函数依赖 A → H 被逻辑蕴涵。

F 为一个函数依赖集,F 的闭包是被 F 逻辑蕴涵的所有函数依赖的集合,记作 F﹢



(3)属性集的闭包

  令 α 为一个属性集,将函数依赖集 F 下被 α 函数确定的所有属性的集合称为 F 下 α 的闭包,记为 α +。

算法:

result := α;
    repeat 
        for each 函数依赖 β → γ in F do
            begin
                if β∈result then result := result ∪ γ;
            end
    until (result 不变)

【实例解析】

给定关系模式 r ( A, B, C, G, H, I) 及函数依赖集:

    A → B
    A → C
    CG → H
    CG → I
    B → H

计算函数依赖集下的 ( AG )+,初始 result = ( AG )

推导过程:

  • 由 A → B,A → B 属于 F,且 A 属于 result,所以 result := result ∪ B。
  • 由 A → C,result = (ABCG)
  • 由 CG → H,result = (ABCGH)
  • 由 CG → I,result = (ABCGHI)
  • 由 B → H,没有新的属性加入,算法终止。

所以由 AG 可以推导出关系中的所有属性,所以 AG 是关系模式 r 的超码。


属性闭包算法的用途:

  • 判断 α 是否为超码,计算 α+,检查 α+ 是否包含 R 中的所有属性。
  • 通过检查是否 β∈α+,可以检查函数依赖 α → β 是否成立。
  • 给出计算函数依赖集 F 的闭包 F+ 的方法:对任意 γ∈R,我们找出闭包 γ+;对任意的 S∈γ+,输出一个函数依赖 γ → S。




2、数据库范式

(以下材料参照整合自:知乎-数据库第一二三范式到底在说什么?)

(1)定义和分类

范式的定义:

符合某一种级别的关系模式的集合,表示一个关系内部各属性之间的联系的合理化程度。

【通俗理解】

  一张数据表的表结构所符合的某种设计标准的级别。就好比一件商品有各个级别的标准,比如国内的国标、出口欧洲的欧标、出口北美的美标等,不同的商品等级标准,意味着不同层次的商品质量,比如一般不出口的商品标准就比较低(奶奶的,真晦气,中国人就不配用好货?


)。数据库也是一样的,范式就是一设计标准,标准越高,数据库的冗余度就越低,维护成本就越低,数据库范式从低到高级别分别有:1NF、2NF、3NF、BCNF、4NF、5NF。它们之间是有联系的,高级别的范式必然满足低级别的范式,关系图如下:



(2)范式的意义

  数据库范式主要是为解决关系数据库中数据冗余、更新异常、插入异常、删除异常问题而引入的设计理念。简单来说,数据库范式可以避免数据冗余,减少数据库的存储空间,并且减轻维护数据完整性的成本。是关系数据库核心的技术之一,也是从事数据库开发人员必备知识。


(3)第一范式(1NF)

  定义:关系模式中所有属性的域都是原子的。
  意义:较细的原子粒度有助于标准化,施加约束,避免输入错误,从而提高数据质量。

【通俗理解】

  符合1NF的关系中的每个属性都不可再分,1NF是所有关系型数据库的最基本要求,如下图表示的情况就不满足 1NF。

  地址可以细分为省、市、街道、详细地址,该关系模式不满足 1NF,应该将地址字段拆分出多个字段。

PS. 一个属性是否是原子的,还得看业务需求,有些业务场景是需要将地址分段的,比如快递的地址,快递公司会根据快递地址进行区域统一仓储运输配送;有些业务场景是不需要将一个完整的地址分段使用的,这时就可以将地址用数据表中一个属性来存储。

  仅满足 1NF 的设计,仍然会存在数据冗余过大、插入异常、删除异常、修改异常等问题,例如下面表的设计

存在的问题:

  • 数据冗余过大:每个学生的学号、姓名、系名、系主任这些数据重复多次。每个系与对应的系主任的数据也重复多次
  • 插入异常:假如学校新建了一个系,但是暂时还没有招收任何学生(比如3月份就新建了,但要等到8月份才招生),那么是无法将系名与系主任的数据单独地添加到数据表中去的 (注1)

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

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

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

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


(4)第二范式(2NF)

  定义:在满足 1NF 的基础上,消除了非主属性对于码的部分函数依赖。

  还是上面的表,根据以下步骤判断是否满足 2NF:

① 找出数据表中所有的
② 根据 ① 中得到的码,找出所有的主属性
③ 数据表中,除去主属性,剩下的都是非主属性
④ 查看是否存在非主属性对码的部分函数依赖

Step 1

  表中所有的函数依赖关系为:

  可以得到表的码只有一个,就是(学号,课名)

Step2

  主属性有两个:学号、课名

Step3

  非主属性有四个:姓名、系名、系主任、分数

Step4

  对于(学号,课名) 姓名,有 学号 姓名,存在非主属性 姓名 对码(学号,课名)的部分函数依赖。
  对于(学号,课名) 系名,有 学号 系名,存在非主属性 系名 对码(学号,课名)的部分函数依赖。
  对于(学号,课名) 系主任,有 学号 系主任,存在非主属性 系主任 对码(学号,课名)的部分函数依赖。


  所以表中存在非主属性对码的部分函数依赖,不满足 2NF 的要求。

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

选课(学号,课名,分数)
学生(学号,姓名,系名,系主任)

  先来判断一下,选课表与学生表,是否符合了2NF的要求?

  对于选课表,其码是(学号,课名),主属性是学号课名,非主属性是分数学号确定,并不能唯一确定分数课名确定,也不能唯一确定分数,所以不存在非主属性分数对于码 (学号,课名)的部分函数依赖,所以此表符合2NF的要求。

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


  模式分解以后新的函数依赖关系如下:


  模式分解后数据表中的数据如下:


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

1、数据冗余是否减少了?

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

2、插入一个尚无学生的新系的信息是否异常?

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

3、删除某个系中的所有学生信息是否会丢失该系的信息?

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

4、李勇转系到法律系是否需要修改多条数据?

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


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


(5)第三范式(3NF)

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

  对于 2NF 中改进的数据库设计中的两个表:

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

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

  为了让数据表设计得到 3NF,进行进一步的模式分解为以下:

选课(学号,课名,分数)
学生(学号,姓名,系名)
系(系名,系主任)


对于选课表,符合3NF的要求,之前已经分析过了。

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

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

  模式分解后新的函数依赖关系如图:

  模式分解后数据表中的数据如下:


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

1、数据冗余是否变少?

  原来学生表每条数据相同系的学生会冗余系名、系主任,现在只有系名。—— 有改进

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

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

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

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


(6)巴斯范式/鲍依斯-科得范式(BCNF)

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

要了解 BCNF 范式,那么先看这样一个问题:
若:

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

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

答:已知函数依赖集:仓库名 → 管理员,管理员 → 仓库名,(仓库名,物品名)→ 数量
  :(管理员,物品名),(仓库名,物品名)
  主属性:仓库名、管理员、物品名
  非主属性:数量

   ∵ 不存在非主属性对码的部分函数依赖和传递函数依赖。
  ∴ 此关系模式属于3NF。


  基于此关系模式的数据表如下:

  虽然已经满足 3NF,但依然存在以下问题:

1、先新增加一个仓库,但尚未存放任何物品,是否可以为该仓库指派管理员?——不可以,因为物品名也是主属性,根据实体完整性的要求,主属性不能为空。

2、某仓库被清空后,需要删除所有与这个仓库相关的物品存放记录,会带来什么问题?——仓库本身与管理员的信息也被随之删除了。

3、如果某仓库更换了管理员,会带来什么问题?——这个仓库有几条物品存放记录,就要修改多少次管理员信息。


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

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


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

仓库(仓库名,管理员)
库存(仓库名,物品名,数量)

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

你可能感兴趣的:([数据库之八] 数据库范式与设计)