一. 关于数据冗余与异常
数据库的规范化
数据库规范化是一种在数据库中组织数据的技术。 规范化是消除冗余(重复)和不良特性(如插入异常,更新异常和删除异常)的方法。 这是一个多步骤的过程,将数据放入表中,再从关系表中删除重复的数据。
规范化主要用于两个目的:
- 消除冗余(无用)数据。
- 确保数据依赖性是有意义的,即数据是有逻辑性地进行存储的。
数据冗余直观上可以说就是一张表里不同位置有大量重复的数据,这种冗余不仅仅增加了存储量,也使得我们会更容易遇到三种异常(插入异常,更新异常和删除异常)。
为了更好的理解这三种异常,我们可以看一下下面这个样例。
学号 | 姓名 | 院系 | 教授 | 教授电话 |
---|---|---|---|---|
401 | Akon | CSE | Mr. X | 53337 |
402 | Bkon | CSE | Mr. X | 53337 |
403 | Ckon | CSE | Mr. X | 53337 |
404 | Dkon | CSE | Mr. X | 53337 |
在上表中,我们有4名计算机科学的学生数据。 他们的院系信息,教授和教授电话记录都是重复的,这就是数据冗余。
插入异常
假设有一个新的学生入学,除非他选择了一个院系,否则学生的数据不能被正确插入,因为我们将不得不将院系信息设置为NULL。
此外,如果我们需要再插入同一院系的100名其他学生的数据,那么所有这100名学生的院系信息都会被重复记录。
更新异常
如果X先生离开大学怎么办? 或者不再是计算机科学系的教授了怎么办? 在这种情况下,所有的学生记录都必须要更新,如果不小心遗漏了任何一条记录,都会导致数据不一致的出现。
删除异常
在我们的学生表中,学生信息和学院信息这两种不同的信息被保存在了一起, 因此在学年结束时,如果所有学生记录都被删除,我们同时也就失去学院信息。
二. 范式(Normal Form)
范式是“符合某一种级别的关系模式的集合,表示一个关系内部各属性之间的联系的合理化程度”。
很晦涩吧?实际上你可以把它粗略地理解为一张数据表的表结构所符合的某种设计标准的级别。就像家里装修买建材,最环保的是E0级,其次是E1级,还有E2级等等。数据库范式也分为1NF,2NF,3NF,BCNF,4NF,5NF。一般在我们设计关系型数据库的时候,最多考虑到BCNF就够。符合高一级范式的设计,必定符合低一级范式,例如符合2NF的关系模式,必定符合1NF。作者:刘慰
链接:https://www.zhihu.com/question/24696366/answer/29189700
来源:知乎
第一范式(1 NF)
要使表格处于第一范式,应遵循以下4条规则:
- 它属性/列 都是不可再分的。
- 存储在任意同一列中的值都属于相同的域
- 表中的所有列应具有唯一的名称。
- 数据存储的顺序并不重要
这其中最关键的规则是第一条:属性不可再分
举例,如下表这样401和492学生选了多于两门课,对于他们俩的"课程"属性是可以再分的,所以这样的表是不符合1NF的。
学号 | 姓名 | 院系 | 课程 |
---|---|---|---|
401 | Akon | CSE | c1,c2 |
402 | Bkon | CSE | c1,c2 |
403 | Ckon | CSE | c1 |
404 | Dkon | CSE | c2 |
可以改为
学号 | 姓名 | 院系 | 课程 |
---|---|---|---|
401 | Akon | CSE | c1 |
402 | Bkon | CSE | c1 |
401 | Akon | CSE | c2 |
402 | Bkon | CSE | c2 |
403 | Ckon | CSE | c1 |
404 | Dkon | CSE | c2 |
但是,就算是这样符合1NF的表仍然会有很多,因为1NF其实是最为基本的要求,不满足1NF的数据库甚至不一定能建立成功。
比如,上面提到的三个异常
- 插入异常。如果学校建了新系,但还没有招生,这个系就不能被插入数据表里
- 更新异常。如果Akon转系了,那在上表中需要更改两行院系&课程记录
- 删除异常。如果所有学生记录被删除,院系记录和课程也就不复存在了
第二范式(2NF)
要使表格处于第二范式,
它应该在满足第一范式的前提下,没有部分函数依赖。
首先我们来了解下什么是依赖。
对于一个表来说,如果通过其中一个属性可以找到唯一对应的一条记录,那么我们可以说它为本表的主键(Primary Key)。
比如下表中,每个学生的学号是存在且唯一的,但是名字可能会有重名存在,学号为主键(Primary Key),而姓名就不是。
同时,我可以通过【学号】查到任何一行的任何一列属性,比如通过【学号】查【院系】,通过【学号】查【课程】,通过【学号】查【姓名】。这时我们可以说,其他的这三个属性依赖于学号。
学号 | 姓名 | 院系 | 课程 |
---|
那什么是部分依赖呢?
候选键(Candidate Key)就是,当两个属性结合在一起可以唯一确定任何一条记录的情况。比如在一张学生成绩表中
学号 | 姓名 | 课程号 | 分数 | 教师 |
---|---|---|---|---|
001 | A | L1 | 90 | Mr.X |
001 | A | L2 | 80 | Mr.Y |
002 | B | L2 | 910 | Mr.Y |
【学号+课程号】 一起可以确定任何一条分数或是学号或是教师,所以 【学号+课程号】 就是本表的候选键。
一张表可以有多个键(key),一般我们会选择其中一个作为主键。
这个时候我们可以看到,教师这一属性其实只由课程号决定,而课程号只是 候选键 的一部分,因此这时我们就说,教师属性存在部分函数依赖。
那么我们要怎样移除部分函数依赖呢。
答案是拆表。
拆表的步骤如下
- 先找出所有的非主属性(不是主键也不是候选键包含部分的属性),在这个例子里,键为【学号+课程号】,那么他俩为主属性,剩下的都是非主属性
- 检查这些非主属性是否存在部分函数依赖。【姓名】只依赖于【学号】,存在;【分数】非得要【学号+课程号】一起才能确定,不存在;【教师】只依赖于【课程】号,存在
将这些存在部分函数依赖的属性分出去建立满足2NF的新表,切分方法并不唯一,
在这里可以这么分
分数表去掉 【姓名】 和 【教师】 属性:
学号 | 课程号 | 分数 |
---|---|---|
001 | L1 | 90 |
001 | L2 | 80 |
002 | L2 | 910 |
为 姓名 建立学生表:
学号 | 姓名 |
---|---|
001 | A |
002 | B |
为 教师 建立 课程表:
课程号 | 教师 | 院系 |
---|---|---|
L1 | Mr.X | CS |
L2 | Mr.Y | EE |
L3 | Mr.Y | EE |
这个时候我们再回头检查一下上面提到的三种异常。
- 插入异常。招新生的话,学生信息可以单独插入,有改进。
- 更新异常。如果L1号课换老师了,只用修改一次,有改进。
- 删除异常。如果删除所有的学生信息,教师信息还在,分数信息也还在;但是如果我从教师表里删掉课程L1的记录,教师Mr.x以及CS院系信息就不复存在了,这是个大问题。
- 数据冗余变少了么?少了。
我们会发现仍然有些问题存在,尤其是删除异常。
这时我们就要提到第三范式了
第三范式(3NF)
第三范式在2NF的基础上,要求不存在任何传递依赖(Transitive dependency)。
什么是传递依赖?以及传递依赖的存在会造成哪些问题?
比如上面一节提到,如果删除教师表里课程L1的信息,教师Mr.x以及CS院系信息就不复存在了,同时,如果一名新来的教师还没有被分配到任何课,他就不能被加入到教师表里。
这是因为,在教师表里:
1.课程号可以决定教师. A → B
2.教师不能决定课程号,因为一个教师可以教多门课. B not→ A
3.教师决定院系,因为一个教师只能属于一个院系. B → C
这时我们就发现,非主属性 【院系】,也依赖于另一个非主属性 【教师】,这种情况就叫做传递依赖。
而3NF的条件,就是要去除这种传递依赖。
解决方法有多种,这里可以将院系信息分表。
课程表只有课程号和教师信息:
课程号 | 教师 |
---|---|
L1 | Mr.X |
L2 | Mr.Y |
L3 | Mr.Y |
而教师表 只有教师和院系信息:
教师 | 院系 |
---|---|
Mr.X | CS |
Mr.Y | EE |
这样我们再检查上面的问题,
删除L1课程信息,Mr.X老师的信息仍然保存的很好,有改进。
新老师Mr.Z可以被插入教师表,哪怕他还没有被分配任何课程。
网友评论