首先,来弄清楚几亿个概念。

数据依赖:主要类型分为函数依赖(Functional Dependency,FD)和多值依赖(Multivalued Dependency)

函数依赖:
  给定一个属性的值,另一个属性的值也会确定。如同数学中的函数,输入一个x值得到一个确定的f(x),所以称为函数依赖。
举个例子:一个属性组F{Sno->Sdept,Sdept->Mname,(Sno,Cname)->Grade}

 

mysql 平凡多值依赖 数据库多值依赖例子_非主属性

 

 

   在这个关系中,Sdept依赖于Sno,Mname依赖于Sdept,Grade依赖于Sno和Cname,记作Sno->Sdept,Sdept->Mname,(Sno,Cname)->Grade。

完全函数依赖:
  
在一张表中,若 X→Y,且对于 X 的任何一个真子集(假如属性组 X 包含超过一个属性的话),X→Y 不成立,那么我们称 Y 对于 X 完全函数依赖,记作 X F→ Y(F应该是在箭头上面)。长下面这样子,别人网页偷的:

mysql 平凡多值依赖 数据库多值依赖例子_函数依赖_02



  简单的解释一下就是如果X中包含1个以上的属性,如上面提到的属性组F中的(Sno,Cname)->Grade,这两个属性放在一起才能唯一确定一个Grade,这就称为Grade完全依赖于(Sno,Cname)。部分函数依赖:
  
与完全函数依赖类似的定义,如果X→Y,但是不完全依赖于X,即X的子集也可以推出Y,那么就称为Y部分函数依赖于X。记做X P→ Y(P应该是在箭头上面),写做:

mysql 平凡多值依赖 数据库多值依赖例子_函数依赖_03

  简单的解释一下,还是上面的那个例子。如果F中的Sno代表的同学只有一门课,那么就算是只有他的学号Sno也可以确定他的成绩Grade,Cname在这个依赖中没有起到作用,这就称为部分函数依赖。

 

传递函数依赖:
  
在一个关系模式R(U)中,如果X→Y,Y→Z.(X不依赖于Y,且Y不属于X),则成Z传递依赖于X。

如果Y→X,即X<->Y,则Z直接依赖于X。记作:

mysql 平凡多值依赖 数据库多值依赖例子_mysql 平凡多值依赖_04

说人话就是x->y,y->z,那么z就是传递依赖于x,中间用y传递了一下。不举例子了,后面具体用途的时候在细说。

 

平凡函数依赖与非平凡函数依赖
设一个关系为R(U),X和Y为属性集U上的子集,若X→Y且X不包含Y,则称X→Y为非平凡函数依赖。
若Y包含于X,则称X→Y称为平凡函数依赖。
说人话就是平凡函数依赖说的是X的子集一定是依赖于X的,非平凡函数依赖就是X外的属性依赖于X。
平凡的没什么研究价值,只要是依赖就必然成立,所以学习的重点在于非平凡函数依赖。


细分可以分为主码、候选码、码。关系是:主码  

mysql 平凡多值依赖 数据库多值依赖例子_mysql 平凡多值依赖_05

  候选码 

mysql 平凡多值依赖 数据库多值依赖例子_mysql 平凡多值依赖_05

  码。

先假设一个场景,在{a,b,c,d},知道a就能确定a、b、c、d的值,知道c、d可以确定a、b、c、d的值。
  1.码:可以确定一个元组所有信息的属性名或者属性名组。
    在上面的场景中,{a}和{c,d}是码,他们可以唯一确定其他属性的值。
  2.候选码:候选码的真子集中不存在码,候选码可以有很多个。
    还是上面的例子,{a}、{c,d}是候选码,他们的子集中不存在码,如果是{a,b},{a,b,c}等则不属于候选码,因为他们的子集都有{a},{a}是码,所以含有码的集合都不属于候选码。
  3.主码:也称为主键,就是任意一个候选码。
    在上面的例子中,{a}和{c,d}都可以是主码。
码还有一点东西,不如上面三个重要,还是讲一下。
主属性和非主属性:包含在任意一个候选码中的属性为主属性,不包含在任意一个候选码中的就是非主属性。
全码:整个属性组都是码,则称之为全码。

 

终于把概念弄清楚了,下面是规范化的具体内容。

规范化:指为了控制由于冗余等带来的问题而要求关系模式满足一定条件的范型,可以通过模式分解来达到,这一过程称之为规范化。规范化的目的是改造不好的关系模式。

范式:关系数据库中的关系模式要满足一定要求,满足不同程度要求的称为不同的范式,第几范式表示关系的某种级别。数据库范式分为1NF,2NF,3NF,BCNF,4NF,5NF,高级范式必定符合低一级范式的要求,比如2NF的关系模式一定符合1NF。

第一范式(1NF):
  定义:(1NF, Normal Form) 如果一个关系模式R中的每个属性A的域值都是原子的,即属性值是不可再分的,则关系模式R属于第一范式,简记为R ∈ 1NF。若数据库模式R中的每个关系模式都是1NF,数据库模式 R∈1NF。
  简单来说,1NF中的所有属性都是不可再分的,如下图所示,学生S仍可以细分为学号Sno和姓名Sname,所以不满足1NF。

学生S

性别Ssex

年龄Sage

专业Sdept

学号Sno

姓名Sname

S1

张三


20

计算机系

  将表格改成以下形式,即可满足1NF:

学号Sno

姓名Sname

性别Ssex

年龄Sage

专业Sdept

S1

张三


20

计算机系

  1NF是所有关系型数据库的最基本要求,如果数据表的设计不满足1NF,那么操作一定是不能成功的。同样的,已经存在于数据库中的表也一定是满足1NF的。

  但是,仅符合1NF的设计,存在数据冗余过大,插入异常,删除异常,修改异常的问题,如:

  关系模式SLC(Sno,Sdept,Sloc,Cno,Grade),Sloc为学生住所。假设同一个系的学生住在同一个地方。函数依赖包括:(Sno,Cno) F->Grade、Sno->Sdept、(Sno,Cno) P->Sdept、Sno->Sloc、(Sno,Cno) P->Sloc、Sdept->Sloc。看着很恶心,画成图:

mysql 平凡多值依赖 数据库多值依赖例子_非主属性_07


  好看多了,但还是看着很烦。

  可以看出,主码为(Sno,Cno)。下面将简单介绍一下几个异常,以便引出第二范式。

1.插入异常:如果Sno=1,Sdept=IS,Sloc=N,但是该学生未选课,因为课程号为主属性,主属性不能为空,所以改条信息无法插入表SLC中,这与现实是不符的。

2.删除异常:如果一个大四学生只有一门课,还是选修课,他上了一节之后想躺宿舍摸鱼,所以他要退课,但是由于课程号是主属性,如果他退课的话课程号属性中没有值,这条操作无法完成,除非删除这个学生的整个元组。就想退个课结果不小心退学了,太惨了。

3.数据冗余度大:还是那个学生,他选了8门课来凑学分,在数据库中他的信息,Sdept,Sloc等无关信息需要重复8次,占用储存空间。

4.修改复杂:因为一个系的人都挤在一起,如果该学生要转系,可能不仅要改Sdept,还要改Sloc。假设他选了8门课,那么他转个系就得改8条数据,很烦。

由以上几个例子,可以发现1NF只是能用而已,并不是一个好的范式,当人数多了之后浪费的资源是非常吓人的,由此引出了第二范式。第二范式(2NF):

  定义:若关系模式R属于1NF,且每一个非主属性都完全函数依赖于R的码,则R属于2NF。

  2NF和1NF的区别就是所有的非主属性都可以由码来确定而不是只由码的一部分确定。还是上文的SLC吧,现成的不用白不用。

  问题的根源在于Sdept、Sloc存在部分函数依赖,Sdept可以由Sno确定,Sloc更离谱我都懒得写,这些奇怪的关系导致我们对数据做修改的时候很费劲,就像是桌子上的电线一样缠在一起,抽一根带十根,想想血压就高了。解决的方法也很简单,把线分开就是了,对应到我们的内容中就是:投影分解法

  投影分解法:

  把SLC分解为两个关系模式以消除部分函数依赖就好了,说人话就是把一张复杂的表拆成两张表。举例子来讲如何分解,Sno->Sdept,Sloc,(Sno,Cno)->Sdept,Sloc,从上面四个关系可以发现,Sdept和Sloc完全可以由Sno唯一确定,码中的另一个属性Cno没有起到作用,那我们就把这个关系单独拿出来做成表,这个表的码只有Sno,这样既消解了部分函数依赖,也没有改变之前的关系。

SC(Sno,Cno,Grade)和SL(Sno,Sdept,Sloc),然后上图:

mysql 平凡多值依赖 数据库多值依赖例子_非主属性_08


  虽然还是挺屎的,但是比之前好看了很多(自欺欺人,我一眼都不想看)。

SC的码是(Sno,Cno),SL的码是Sno,这样就消解了所有的部分函数依赖。只剩下了完全函数依赖,来一张图解释一下:

mysql 平凡多值依赖 数据库多值依赖例子_部分函数依赖_09


这样看来,就没有非主属性对码的部分函数依赖了。不过Sloc对Sdept还是存在函数依赖,这是第二范式管不到的地方,不过相对于第一范式已经做出了非常大的改进,下面来具体分析一下。1.插入异常:学生选课和学生基本信息分别在两个表中,所以即使没有选课的学生也可以加入SL。

2.删除异常:删除一个学生的选课记录只影响SC,对SL没有影响,大四老哥不用退学了,好耶!

mysql 平凡多值依赖 数据库多值依赖例子_mysql 平凡多值依赖_10


3.数据冗余度大:选课跟基本信息不在一张表里,重复的都是必要数据,大大降低了数据冗余。

4.修改复杂:数据都不怎么冗余了,修改操作自然也随之减少。

但是!划重点,追求进取的人类永远不会停止进步的步伐。

刚才其实也提到了,SL中仍存在着对于我们简化关系结构有障碍的关系,即Sloc对于Sdept的函数依赖,这个关系我们是不想要的,他看上去很多余,明明已经可以通过Sno确定Sloc,可是Sloc偏偏还和Sdept有不得不说的关系,这个关系也导致SL存在一些问题。

mysql 平凡多值依赖 数据库多值依赖例子_部分函数依赖_11


1.插入异常:一个系刚刚成立,还没有学生,那么码Sno就没有数据,这个系就没了,系长哭泣。

2.删除异常:某系决定暂停招生一段时间,把最后一批学生送走时候,删除学生数据,Sno又空了,这个系又没了,系长又哭了。

3.数据冗余大:一个系的所有学生都住在一起,可是Sloc这个属性却每个学生重复一次。

4.修改复杂:同上,Sloc重复的多,如果该系调整住址位置,修改的也多。

   基于以上的这些问题,为了更好的提高数据库的效率,必须引入第三范式(3NF)。

第三范式(3NF):

  定义:关系模式R<U,F>中若不存在这样的码X,属性组Y及非主属性Z(Z不属于Y),使得Y函数依赖于X,X不函数依赖于Y,Z函数依赖于Y,成立,则成R属于3NF。

  3NF和2NF的区别就在于不存在非主属性依赖于其他非主属性或者属性组。

  对于上图中的SL,显然存在非主属性Sloc依赖于Sdept,这样是不行的。所以我们只要将这个表分成两个表,把Sno和Sloc之间的关系断掉即可。

mysql 平凡多值依赖 数据库多值依赖例子_mysql 平凡多值依赖_12


  把Sno和Sloc的直接关系断掉,Sno依然可以通过先找到对应的Sdept,然后再通过Sdept找到Sloc,效果没变,但是减少了无用的关系。

  将新的表命名为SD和DL。

mysql 平凡多值依赖 数据库多值依赖例子_非主属性_13


  这样我们就把SL这个2NF分解成了两个3NF,一定程度上解决了之前提到的问题:

1.插入异常:新建立的系可以直接放入DL中,与学生无关。
2.删除异常:学生数据在SD中,删除不会影响到DL中的内容。
3.数据冗余大:DL中的Sdept和SLoc的内容只会存储一次。
4.修改复杂:同上,Sloc无重复,如果该系调整住址位置,修改只需要修改一条内容即可。

 

先稍微总结一下:1NF只要求每个属性不可再分,2NF要求非主属性要完全函数依赖于码,3NF则要求非主属性不能依赖于其他非主属性或者属性组。

再来讲一点点细节:
1.3NF的关系中,每一个非主属性既 不部分函数依赖于候选码,也 不传递依赖于候选码。
2.使用投影分解法将2NF分解为3NF可以消解以上提到的问题,但是不能完全消除各种异常情况和数据冗余。

下面来介绍一下3NF中存在的问题:

  这次需要变换一下场景。在关系模式STJ(S,T,J)中,S代表学生,T代表老师,J代表课程。

mysql 平凡多值依赖 数据库多值依赖例子_部分函数依赖_14


mysql 平凡多值依赖 数据库多值依赖例子_函数依赖_15


这与我们之前遇到的情况又有所不同,码中的属性J函数依赖于非主属性T。我们仔细分析一下,非主属性T和J都完全函数依赖于码,他是2NF,两张表都只有一个非主属性,他是3NF。在这个情况下出现了3NF管不到的T->J,又导致了老生常谈的几个问题:

1.插入异常:若某教师想开设课程,由于没有学生,无法插入。
2.删除异常:某门课程结课,删除学生元组的同时会丢失教师开设这门课程的所有信息。
3.数据冗余大:一个教师只教一门课,但是每个元组都要重复该老师和课程的信息。
4.修改复杂:课程如果改名,所有的学生元组都要做修改。

仔细观察一下可以发现,这些问题的根源都在于T->J这个关系,即存在着主属性对于码的部分函数依赖与传递函数依赖,没有这个关系万事大吉。即存在非主属性和主属性的关系。由此引出BC范式(BCNF)(Boyce Codd Normal Form)。

BC范式(BCNF):
  
定义:关系模式R<U,F>属于1NF,若对于R的每一个函数依赖X->Y,若Y不属于X,则X必含有候选码,那么R属于BCNF。

  人话:每一个决定属性集都包含候选码即可

  举个例子:

mysql 平凡多值依赖 数据库多值依赖例子_部分函数依赖_16


这样将STJ分为两张表,这样一来就没有任何属性对码的部分函数依赖和传递函数依赖,同时依旧满足之前的关系,也解决了上述的四个问题:

1.插入异常:即使这门课没有学生选修,TJ中依然可以保存老师和课程的信息。
2.删除异常:删除学生信息只会修改SJ,不会对TJ中的内容造成影响。
3.数据冗余大:一个老师开设课程的相关内容只会出现一次。
4.修改复杂:课程改名只需要修改TJ中的元组即可。

BCNF范式一般认为是3NF更进一步,是修正的第三范式,所以有时也被称为第三范式。
BCNF的关键在于实现没有任何属性对码的部分函数依赖和传递函数依赖,这也是BCNF和3NF最大的区别。