数据库进阶系列之二(细说数据库范式)

理论性的东西,往往容易把人人都看得懂的东西写成连鬼都看不懂,近似于主任医生开的药方。从前学范式的时候,把书中得概念翻来覆去看,看得痛心疾首深恶痛绝,再加上老师深切误导,最后一塌糊涂。借助网络资源,自己写了一篇,自己是看懂了,希望对大家也有所帮助,有错误帮忙指正。

数据库范式(Normal forms):是用于规范关系型数据库设计,以减少谬误发生的一种准则。

1NF(first normal form):
Table faithfully represents a relation and has no repeating groups.
数据库表必须如实地展现“关系”,并且不允许有“重复组”出现。

这样的概念真是令人痛心疾首,我们只好再搬出1NF的的作者之一Chris Date的解释:
1. There's no top-to-bottom ordering to the rows.
(任意两行没有特定的顺序关系。不存在一个特定的理由要某一行必须在另一行之前。)
2. There's no left-to-right ordering to the columns.
(任意两列没有特定的顺序关系。)
3. There are no duplicate rows.
(不允许存在重复的行。如果一张表没有Unique Key,事实上它是违反1NF的。)
4. Every row-and-column intersection contains exactly one value from theapplicable domain (and nothing else).
(不允许出现空值Null,这一点不同作者是有争议的。事实上我们常常违背这点。)
5. All columns are regular [i.e. rows have no hidden components such as row IDs,object IDs, or hidden timestamps].
(不允许存在隐藏字段。不知道Oracle的Rowid属不属于这个?)

有人从第四点的“one value”大肆挖掘,于是我们就见到了书上这样的定义:“如果一个关系模式R的所有属性都是原子的,即不可再分的基本数据项,则R?1NF”。

这一点被认为是1NF的核心,“关系模式R”?“表”,“属性”? “列”,下面是一种与1NF不一致的情况,通常这是一类很明显的设计缺陷:

ID
Artist
FavoriteColor
……
1
Babyface
Blue,Yellow
……
2
Sting
Green
……

对上例我们不能把它拆分成FavoriteColor1、FavoriteColor2……因为首先我们不能确定该拆分成几列;其次FavoriteColor1与FavoriteColor2在结构、含意方面都是相同的,这实际上也是一类“repeating group”;同时这种设计会导致某些查询困难,比如“有哪些艺人喜欢黄色?”

解决方案是将表拆分成两个:
ID
Artist
……
1
Babyface
……
2
Sting
……

ID
FavoriteColor
1
Blue
1
Yellow
2
Green

总结:
对1NF最核心的“原子性”,违反此规范的可能性:接近于0%。不过,网上很多帖子说在关系型数据库中根本不可能违背1NF,我认为这是不对的。



2NF(second normal form):
No non-prime attribute in the table is functionally dependent on a part (proper subset) of a candidate key.
不存在非主属性对任一候选键的部分函数依赖。

如果解释完下面几个概念,这个定义就可以读懂了:
Superkey:超级键(L),如果属性或属性组合能唯一标识一条记录,则它是一个Superkey。
Candidate key:候选键,当Superkey只包含一个属性时,则它是一个候选键;当Superkey包含一组属性时,仅当这一组属性不包含另一Superkey时,它是一个候选键。换句话说,候选键是“纯净的”、最小化的Superkey。
Non-prime attribute:非主属性,未在任何候选键中出现的属性,即为非主属性。

举例来说,对表{First_name,Last_name,Address},假定全名不重复,则:
Superkey:
{First_name,Last_name}
{First_name,Last_name,Address}

Candidate key:
{First_name,Last_name}

Non-prime attribute:
Address

浅白版:“2NF针对的是复合候选键(即键包含的字段个数>1)的情况,非主属性不能只依赖于复合候选键中的一部分字段。”显然,如果是非复合候选键,如果它符合1NF,那么它一定符合2NF。

假设有这样一张涉及艺人与唱片公司的关系表:
Artist
艺人
Company
唱片公司
DurationYears
签约总年数
CompAddr
公司住址
Babyface
Solar
4
Indiana
Babyface
Laface
2
Indiana




显然,{Artist,Company}为可以作为一个候选键,DurationYears在这没有问题,但CompAddr是违反2NF的,它只依赖于候选键的一部分(依赖于Company),这是违反2NF的,为了消除这种情况,我们可以:
Artist
艺人
CompID
唱片公司
DurationYears
签约总年数
Babyface
1
4
Babyface
2
2

ID
Company
唱片公司
CompAddr
公司住址
1
Solar
Indiana
2
Laface
Indiana

总结:
对于2NF,如果关系中的候选键只包含一个属性,可以直接略过。

在考虑2NF的过程中,不要把几个无关的实体的属性杂揉放在一个关系中,比如Artist是一个实体、Company是一个实体,它们可以有一系列的关联表(也是实体),但在关联表中尽量不要引入前两个实体的无关属性。



3NF(Third normal form)
Every non-prime attribute is non-transitively dependent on every key of the table.
不存在非主属性对任一键(候选键)的传递依赖。

传递依赖,你可以顾名思义,这里就不再引入定义了,举个例子,有下面一张表:
Tournament
赛事
Year
年份
Winner
冠军
Winner Date of Birth
冠军生日
Indiana Invitational
1998
Al Fredrickson
21 July 1975
Cleveland Open
1999
Bob Albertson
28 September 1968
Des Moines Masters
1999
Al Fredrickson
21 July 1975
Indiana Invitational
1999
Chip Masterson
14 March 1977

这里的候选键为{Tournament,Year},显然有这样的决定关系:
{Tournament,Year}→Winner
{Tournament,Year}→Winner→Winner Date of Birth
其中第二条就属于违反3NF的情况,因为Winner Date of Birth依赖于Winner而不是直接依赖于候选键。这种情况下,可以将Winner,Winner Date of Birth单独作为一张表,这里不赘述。

总结:
我觉得大多数人凭借直观感觉,就可使设计的关系符合3NF,所以这些理论,你只需要姑且读之。



BCNF(Boyce-Codd normal form)(Boyce与Codd是该范式的两名作者。)
Every non-trivial functional dependency in the table is a dependency on a superkey.
表中的任何非平凡函数依赖,都必须是对superkey的依赖。

non-trivial functional dependency:非平凡函数依赖,如果存在一个决定关系x→y,且y并非x的子集,则叫着y非平凡函数依赖于x。

BCNF与3NF的最大区别是它并不仅针对非主属性(non-prime attribute)来说,它发生的时候常常是表中根本不存在非主属性,以至于它不可能违反2NF或3NF。而BCNF的出现就是为了扩大“打击面”。

于是BCNF的主旨是:补充对发生在主属性(prime attribute)身上的函数依赖的约束,因为对于非主属性的约束已经在3NF中完成了。

例子,使用关系表描述学生、课程、教师的关系(假定一名教师只负责一门课程,一门课程则可以由多位教师负责):
Student
学生
Course
课程
Teacher
教师
S1
C1
T1
S1
C2
T2
S2
C1
T1
S2
C2
T3
S2
C3
T2
候选键:
{Student,Course}
{Student,Teacher}
因此这里不存在非主属性,而在主属性的函数依赖中,存在Teacher→Course,这属于违反BCNF的情况。

可是,问题是这个表看起来还挺正常的啊?!它的毛病在于,我们无法阻止类似最后一行这样的数据插入,而这会导致与前提“一名教师只负责一门课程”违背。所以我们还是需要将它拆分:
Student
学生
Teacher
教师
S1
T1
S1
T2
S2
T1
S2
T3

Teacher
教师
Course
课程
T1
C1
T2
C2
T3
C2
这样,在“Teacher-Course”表中,借助主键的帮助,最后可以避免违背“一名教师只负责一门课程”这个前提。

那么,如果没有这样一个前提,是初的设计是否符合BCNF?目前看来是的。

真实的情况可能更为复杂,下面这个更接近于我的一些经历:
1)学生需要学习多门课程
2)一门课程可能有多位教师负责
3)一位教师可能负责多门课程
4)某一班级的某一课程对应的教师是固定的(一位)
据此,为了描述学生、课程、教师三者的关系,从这一团乱麻中最早跳出来的大概是这样的表:
Student
学生
Class
班级
Course
课程
Teacher
教师









候选键:
{Student,Course}
我们可以明显地看到Student→Class违反了2NF,于是:
Student
学生
Class
班级





Class
班级
Course
课程
Teacher
教师







从这两张表,仔细考虑,即便我们通过Class关联两张表,还是无法得出学生与课程的关系(只能得出可供该学生选择的课程),所以我们需要再添加一张表:
Student
学生
Course
课程





最后大概是这么三张表,可能还有其它的方案,这里只是举例说明,就不纠缠了。

【数据库进阶系列之二(细说数据库范式)】在BCNF之后,还有4NF,5NF,DKNF,6NF,等什么时候有空了再看看是什么东东。

    推荐阅读