范式数据库-现代管理学的研究范式与企业管理理论的研究范式
前言
通常,我们设计的数据库大部分都是符合第三范式或者 BC 范式的,但是,本着知其然,还要知其所以然的精神,我们还是要了解这些范式解决了什么问题,还未解决什么问题,这些问题带来的影响是什么。
什么是范式?
数据库范式,又称为数据库规范化,用于保证数据库之间的关系合理,减少数据库中的数据冗余,消除操作带来的异常,增进数据的一致性。
下面是维基百科中的解释:
Database normalization is the process of structuring a relational database in accordance with a series of so-called normal forms in order to reduce data redundancy and improve data integrity. It was first proposed by Edgar F. Codd as part of his relational model.
用一句话来概括就是:数据库设计范式可以帮助我们减少数据的冗余,同时提高数据的完整性。
前置知识
在学习数据库范式之前,我们必须了解一些前置知识,这些知识在后续的内容将会大量使用,如果对这些不了解,那么将会对后面的内容一头雾水。
首先,我们定义一张表,并添加一些数据,这个表 1 (选课表)有助于我们理解这些概念:
码
关系中的某个或者某几个属性的集合,用于唯一地标识每一条数据(这里的每一条数据就是数据库中的每一条记录)。
候选码
在一个表的关系中,可以存在多个关系集合用于唯一确定一条记录,这些多个集合就称为候选码,也称为候选键。候选码可以存在多个,每一个候选码都可以唯一地确定一条记录。
我们换一种更加严谨的说法:假设 K 为某个表中的一个属性或者属性组,如果除去 K 之外的所有属性都完全函数依赖(稍后会介绍)于 K,那么我们就称 K 为候选码。
根据上表的示例我们可以得出一个候选码:
主码
通常我们会从候选码中选择一个码作为主码,也就是我们通常所说的主键。
函数依赖
在数学上的解释是:
,输入一个 X,可以得到一个确定的 Y。有点类似于纯函数。
对应到一个表上就是,在一个属性(属性组)X 确定的情况下,必定能够确定属性 Y 的值,这就能够称作 Y 函数依赖于 X,写作 X -> Y 。
记作:
比如下面这些关系都存在函数依赖:
但是,下面这些关系就不存在函数依赖:
完全函数依赖
在一个表中,如果存在 X -> Y,那么对于 X 下的任何一个真子集(X’),X’ -> Y都不成立,那么我们就说 Y 对于 X 完成函数依赖。
记作:X ->F Y
通俗的说,必须通过码中的所有属性才可以唯一确定一个值。比如:
部分函数依赖
在一个表中,如果存在 X -> Y,但是 Y 并不完成依赖于 X。存在一些 X 的子集 X’,X‘ -> Y成立,那么我们就说 Y 对于X 部分函数依赖。
记作:X ->P Y
通俗的说,只需要码中的部分属性即可唯一确定一个值。比如:
它跟完全函数依赖的区别在于,完全函数依赖必须要通过码中的所有属性才可以唯一确定一个值,而部分函数依赖只需要码中的部分属性即可。
传递函数依赖
如果 Z 函数依赖于 Y,Y 函数依赖于 X,并且 X 不函数依赖于 Y,那么我们就说 Z 传递函数依赖于 X。
记作:X ->T Z
通俗的说,通过码可以唯一确定一个属性,然后通过该属性可以唯一确定另一个属性,所以就演变为了可以通过码唯一确定一个无函数依赖的属性。
属性
属性就是我们在表中定义的每一个列。
主属性
在码中的所有属性(每一列)都称为主属性
非主属性
除了主属性之外的其他属性,都称为非主属性。
范式
有了上面的那些概念,我们现在就可以正式来谈谈范式了。数据库设计范式有很多种:1NF、2NF、3NF、BCNF、4NF、5NF、6NF 等等。
但是并不是说遵循的范式等级越高越好,范式过高虽然具有对数据关系有更好的约束性,但是也会导致表之间的关系更加繁琐,从而导致每次操作的表会变多,数据库性能下降。
通常,在我的设计中,最高也就遵循到 BCNF,普遍还是 3NF。
1NF
1NF,又称第一范式。只要每一列中的数据都不可再分即可满足该范式,关系型数据库建立的表默认都支持该范式。但是一些开发人员会反其道而行,比如下面这种表 2 设计:
这种设计是违反了第一范式的设计范式数据库,因为这里的地址和亲属是可以继续划分的,而第一范式强调的就是属性不可再分。
结论:属性不可再分。
2NF
1NF 只是设计数据库最基本的要求,但是数据会存在大量的冗余,并存在删除异常、插入异常、更新异常。我们把目光看到我们一开始的表 1 中。
在该表中,存在下面几个问题:
为了解决上述的问题,我们就要引入更高一级的第二范式(2NF)。
2NF 对 1NF 做了哪些改进呢?
2NF 在 1NF 的基础上,消除了非主属性对码的部分函数依赖 。我们已经知道码是可以唯一确定一条记录的属性集合,非主属性是除了码中属性之外的其他属性。
我们来分析表 1:
码:(学号、课名)
主属性:(学号、课名)
非主属性:(姓名、系名、系主任、班级、课名、分数)
我们回过头来分析一下表 1 是否符合 2NF,要符合 2NF 那么就不能存在非主属性对码的部分函数依赖。判断是否符合 2NF 可以通过以下步骤:
找出表中所有的码(候选码)。根据第一步得出的码找出所有的主属性。除了主属性之外的其他属性,就都是非主属性。判断是否存在非主属性部分函数依赖于码。
我们根据上面的步骤来验证表 1 是否符合 2NF。
第一步
找出表中所有的候选码。
如何找候选码呢?只有使用排列组合来寻找了。
以此类推,一直到所有属性组成的属性组为止。这看起来相当的繁琐,但是我们一般都可以通过直接观察得到,或者我们设计的时候就可以确定我们拥有的候选码有哪些。并不需要一个个的的自己组合判断。
如果一定要自己组合判断,我们可以根据候选码具有完全函数依赖的特性,可以推测出:如果 A 是码,那么任何与 A 组合的属性组都不符合码的要求,比如:(A,B)(A,B,C)等等。
我们根据上面的解释,我们在这一步中可以得出表 1 中的候选码:(学号、课名)
第二步
根据码找出所有的主属性。
这一步相对简单,第一步中找到的码只有(学号、课名)。所以主属性只有(学号、课名)。
第三步
除去主属性之外的其他属性,都是非主属性。
所以非主属性有(姓名、系名、系主任、班级、分数)。
第四步
判断第三步找出的非主属性是否部分函数依赖于第一步找出的码。
我们根据上面的这张图,可以看出,有多个非主属性部分函数依赖于码。
所以对于表 1 来说,它不符合 2NF 的要求,它只能达到 1NF 的要求。
在这种情况下,我们应该如何改进才能让表 1 符合 2NF 的要求呢?当然是将表进行拆分,消除这些部分函数依赖,有个比较正式的名称:模式分解。
我们可以将选课表进行拆分,将它拆分为两张表:选课表、学生表。
我们现在再来看选课表。此时它只有一个码:(学号,课名),非主属性只有一个:分数。它们之间已经不是部分函数依赖的关系了,而是完全函数依赖的关系。选课表中已经不存在部分函数依赖,所以此时选课表符合第二范式(2NF)。
我们现在来看看学生表是否符合 2NF。
按照上面的四个步骤:
码:(学号)主属性:学号非主属性:(姓名,系名,系主任,班级)因为码中只有一个属性,所以不可能存在部分函数依赖。
所以学生表也是符合 2NF 的。
结论:在 1NF 的基础上范式数据库,通过对表的拆分,消除表中非主属性对码的部分函数依赖。
3NF
我们通过 2NF 已经将表 1 拆分成了两个表:选课表、学生表。但是依然存在着一些问题,比如:
我们通过上一节已经知道选课表和学生表都是符合 2NF 的,但是学生表中依然存在着一些问题。所以,符合 2NF 的表不一定就能满足我们的要求。此时,我们就要用到 3NF。
只要表中存在非主属性对码的传递函数依赖,则不满足 3NF 的要求。所以对于 3NF 来说,就是在 2NF 的基础上,消除非主属性对码的传递函数依赖。
我们来分析以下学生表中的函数依赖关系:
找出学生表中的码:(学号)主属性:学号非主属性:(学号,系名,系主任,分数)学号与系主任之间存在传递函数依赖
通过图可以看到,只要我们确定了学号,我们就可以确定系名,确定了系名,我们就可以确定系主任。因此在学号确定的情况下,我们也可以确定系主任。这就是传递函数依赖。
消除传递函数依赖的方法也是通过拆分表来完成,我们将学生表拆分为两张表:学生表、系表
在拆分之后,学生表就消除了函数传递依赖,所以此时学生表符合 3NF,系表也符合 3NF。
结论:在 2NF 的基础上,消除表中非主属性对码的传递函数依赖。
我们为了使表 1 符合设计范式,将表 1 拆分成了三个表。从而解决了数据的冗余、删除异常、添加异常、更新异常的问题。
但是这也带来了一个新的问题,我们查询数据的时候不能只通过查询一张表来得出结果,而要通过连接三张表一起查询才行,这也会影响数据库的查询性能。
BCNF
我们已经了解 1NF、2NF、3NF,也通过这三个范式解决了问题,那么为什么还会有 BCNF 呢?这个范式又解决了什么问题?我们带着这两个问题一起来看看 BCNF。
首先,要说明的是,上面经过拆分之后的三张表都是符合 BCNF的。看到这里,小朋友,你是否有很多问号?
别急,让我们先看一下 BCNF 的定义:不存在主属性对于码的部分函数依赖和传递函数依赖,那么即符合 BCNF。
注意:这里说的是主属性,而前面的 2NF、3NF都是非主属性对码的部分函数依赖和传递函数依赖。要注意这个区别,很重要。
我们可以看到上面三个表因为都只有一个码,所以不存在主属性对码的部分函数依赖和传递函数依赖。不符合BCNF 需要存在两个码以上。
比如下面这种情况:
我们来分析一下这个表:
非主属性只有“数量”,而非主属性对于两个码都不存在部分函数依赖和传递函数依赖,所以这个表是符合 3NF的。但是这个表却是不符合 BCNF 的。
因为存在主属性对码的部分函数依赖:
那么我们要将该表进行拆分才能让物品表符合 BCNF。将该表拆分为两个表:仓库表、物品表。
我们可以来看看拆分之后解决了什么问题:
所以消除了主属性对码的部分函数依赖和传递函数依赖之后,数据库中的操作的异常就不再出现了。
结论:在 3NF 的基础上,消除主属性对码的部分函数依赖和传递函数依赖。
总结
同上上面的分析可见,我们在平时设计表的时候,虽然不知道这些范式,但是大部分都是有遵循这些范式的,至少也会遵循 2NF。因为只要我们对每个独立的个体建立表,大部分都能符合 3NF。
但是,我们不能因此而放弃理论知识,因为理论知识才是支撑你设计出更好的数据库结构的基础。
本文大量参考了如何理解关系型数据库的常见设计范式?中的表述。如果需要对数据库范式有更深入的理解,可以查阅该网页。
参考如何理解关系型数据库的常见设计范式?数据库规范化Database normalization