函数依赖
记 A->B 表示 A 函数决定 B,也可以说 B 函数依赖于 A。
对于 A->B,如果能找到 A 的真子集 A’,使得 A’-> B,那么 A->B 就是部分函数依赖,
比如:(学号,学生姓名)-> 院系
但也可以直接:学号 -> 院系
否则就是完全函数依赖。
比如:(学号,课程号)-> 成绩
不可拆开
另一种解释:对于(A,B,C)
有(A,B)->C,且C不能单独通过A或B得到,则该依赖为完全函数依赖。
有(A,B)->C,且C可以单独通过A或B得到,则该依赖为部分函数依赖。(会造成冗余)
有 A->B,B->C,则 A->C 是一个传递函数依赖。(会造成冗余)
异常
以下的学生课程关系中,{学号, 课程号} 为键码,有如下函数依赖:
学号 -> 学生姓名,院系
院系 -> 院长姓名
课程号 -> 课程名
(学号,课程号) -> 成绩
学号 | 学生姓名 | 院系 | 院长姓名 | 课程号 | 课程名 | 成绩 |
---|---|---|---|---|---|---|
学生1 | 学生A | 学院1 | 院长A | 课程1 | 课程A | 90 |
学生2 | 学生B | 学院2 | 院长B | 课程1 | 课程A | 80 |
学生2 | 学生B | 学院2 | 院长B | 课程2 | 课程B | 100 |
学生3 | 学生C | 学院3 | 院长C | 课程2 | 课程B | 95 |
不符合范式的关系,会产生很多异常,主要有以下四种异常:
- 冗余数据:例如
学生B
出现了两次。 - 修改异常:修改
学生B
一条记录中的某个信息,但另一条记录中相同的位置却没被修改。 - 删除异常:删除
学生B
一条记录中的某个信息,那么该条记录中的其他信息也会丢失。 - 插入异常:例如想要插入一个学生的信息,如果这个学生还没选课,那么就无法插入。
范式
范式是为了消除重复数据减少冗余数据,让磁盘空间得到更有效利用的一种标准。
高级别范式的满足低级别的范式,比如2NF一定满足1NF。
1. 第一范式 (1NF)
属性不可分,均为简单属性。但四种异常均无法解决。究其原因是其中存在各种各样的函数依赖关系。
例子中的关系就是一个1NF。
2. 第二范式 (2NF)
满足1NF,并且每个非主属性完全函数依赖于键码。
可以通过分解来满足。
分解前
{学号, 课程号} 为键码。有如下函数依赖:
学号 -> 学生姓名,院系
院系 -> 院长姓名
课程号 -> 课程名
(学号,课程号) -> 成绩
成绩
完全依赖于键码,它没有任何冗余数据,每个学生的每门课都有特定的成绩。学生姓名
、院系
和课程名
都部分依赖于键码,当一个学生选修了多门课时,这些数据就会出现多次,造成大量冗余数据。院长姓名
传递依赖于键码。 (2NF不考虑)
分解后
关系1
学号-> 学生姓名, 院系
院系-> 院长姓名
关系-2
学号, 课程号-> 成绩
关系-3
课程号 -> 课程名
3. 第三范式 (3NF)
满足1NF,并且非主属性不传递函数依赖于键码。
上面的 关系1 中存在以下传递函数依赖:
学号-> 院系-> 院长姓名
可以进行以下分解:
关系-11
学号-> 学生姓名, 院系
关系-12
院系-> 院长姓名
背诵版
对象拆分的好,基本上就满足三大范式了。
- 第一范式,字段不可拆分,原子性
- 第二范式,联合主键情况,字段都应与主键有关联。如成绩表,主键学生id,学科id,不应该出现学生班级信息之类的字段。
- 第三范式,表不包含多余属性,学生属性属于学生,班级属性属于班级。