第一范式(1NF)
第一范式是指每一列都是不可分割的原子数据项。
举个例子,以姓名、联系方式、户籍创建一个表如下:
姓名 | 联系方式 | 户籍 |
张三 | 23215613211 | 中国四川省成都市 |
李四 | 44654984654 | 中国湖北省武汉市 |
显然,这就不符合第一范式,因为户籍可以分割为国家、省、市,甚至是分割到区、街道等等。要使其符合第一范式,只需对户籍进行分割即可,如下:
姓名 | 联系方式 | 国家 | 省 | 市 |
张三 | 23215613211 | 中国 | 四川 | 成都 |
李四 | 44654984654 | 中国 | 湖北 | 武汉 |
那么,第一范式的意义在哪里呢?
个人理解,符合第一范式的表可以将数据分割得更细致,在进行某些操作时会更加方便,举个例子,若要以上述表来查找户籍在成都的人,显然不符合第一范式的表查询起来就不太方便了,而符合第一范式的表就显得更加方便。不过这也并非是绝对的,需要按照实际情况来设计。
第二范式(2NF)
第二范式是指在满足第一范式的条件下,除主键外的每一列都完全依赖于主键(主要针对于联合主键而言)。
比如新建一个表如下:
学号 | 姓名 | 所在系 | 课程名 | 学分 | 成绩 |
---|---|---|---|---|---|
1 | 张三 | 物理系 | A | 3 | 80 |
1 | 张三 | 物理系 | B | 2 | 93 |
1 | 张三 | 物理系 | C | 2.5 | 87 |
2 | 李四 | 化学系 | A | 3 | 95 |
2 | 李四 | 化学系 | B | 2 | 89 |
2 | 李四 | 化学系 | C | 2.5 | 95 |
显然,这个表的主键为(学号,课程名),对于其他的非主属性来说,
姓名由学号即可唯一标识,是对主键的部分依赖;
所在系由学号即可唯一标识,是对主键的部分依赖;
学分由课程名即可唯一标示,是对主键的部分依赖;
成绩由学号和课程名联合标识,是对主键的完全依赖。
可见,其中姓名、所在系、学分都不是对主键的完全依赖,因此该表不满足第二范式。
从中我们也可以看出该表的问题:
1.数据冗余
对于同一个人,学号、姓名、所在系在课程名不同的情况下多次出现,如果有n门课程,这些就会重复出现n-1次;
2.更新异常
比如说课程A的学分修改了,那么在张三下面的A学分需要修改一次,李四下面的A学分也需要修改一次,王五.... 这样就会进行多次重复的修改;
3.插入异常
如果新开了一门课程D,刚好此时没有学生选该课,由于没有学生选择,缺少主键中的“学号”,因此就无法正常插入数据到表中;
4.删除异常
如果新开的课程D只有王五选了,而后来王五退学了,那么就应当将王五的数据从数据库中删除,而此时删除的话,就会把王五所选的D课程的相关信息一并删除,这样就无法保留课程信息了。
为了满足第二范式,就可以将原表进行拆分,如下:
学号 | 姓名 | 所在系 |
---|---|---|
1 | 张三 | 物理系 |
2 | 李四 | 化学系 |
学号 | 课程名 | 成绩 |
---|---|---|
1 | A | 80 |
1 | B | 95 |
1 | C | 78 |
2 | A | 87 |
2 | B | 93 |
2 | C | 80 |
课程名 | 学分 |
---|---|
A | 3 |
B | 2 |
C | 2.5 |
这样,就可以避免数据冗余、更新异常、插入异常和删除异常了。
第三范式(3NF)
第三范式是指在满足第二范式的基础上,每一条数据不能依赖于其他的非主属性,也就是消除了传递依赖关系。
以上述拆分后满足第二范式的表(学号,姓名,所在系)为例,添加新属性‘系所在地’,并扩展如下:
学号 | 姓名 | 所在系 | 系所在地 |
---|---|---|---|
1 | 张三 | 物理系 | 物理大楼 |
2 | 李四 | 化学系 | 化学大楼 |
3 | 王五 | 物理系 | 物理大楼 |
4 | 赵六 | 物理系 | 物理大楼 |
首先,添加新属性后的表也是符合第二范式的,即姓名、所在系以及系所在地对于主键‘学号’不存在部分依赖,但是其中的系所在地实际上是直接依赖于所在系,传递依赖于学号的,也就是说,通过学号,可以唯一确定所在系,然后通过所在系来确定的系所在地,这样就叫做不满足第三范式。
根据上面的表可以看出,如果不满足第三范式,也会出现数据冗余(物理大楼多次出现)、更新异常(如果物理系搬家,那么就需要全部修改)、插入异常(如果新成立一个系,但是还未招生,缺乏主键学号,导致无法插入)以及删除异常(删除李四的信息,实际上就把数据库中的所有化学系相关信息删除了)