数据库的规范化设计方法---3种范式
第一范式(1NF):确保表中的每个字段都是不可分割的基本数据项。
第二范式(2NF):在满足1NF的基础上,确保非主属性完全依赖于主键。
第三范式(3NF):在满足2NF的基础上,确保非主属性不传递依赖于主键。
使用 StudentCourse
表作为基础,来解释第一范式(1NF)。
第一范式 (1NF) 的核心要求:
确保表中的每一个单元格(字段值)都包含单一、不可再分割的基本数据项。换句话说,表中的每一列都必须是原子性的,不能包含多个值或复合值。
回顾之前的 StudentCourse
表:
StudentID (学号) | StudentName (姓名) | StudentDepartment (系别) | CourseID (课程号) | CourseName (课程名) | Professor (教授) |
---|---|---|---|---|---|
1001 | 张三 | 计算机系 | CS101 | 数据结构 | 李教授 |
1001 | 张三 | 计算机系 | CS201 | 数据库原理 | 王教授 |
1002 | 李四 | 电子系 | EE101 | 电路基础 | 赵教授 |
1003 | 王五 | 计算机系 | CS101 | 数据结构 | 李教授 |
1003 | 王五 | 计算机系 | CS301 | 操作系统 | 刘教授 |
分析 StudentCourse
表是否满足 1NF:
现在我们来逐一检查这个表中的每一列,看看它们是否都满足原子性(不可分割)的要求:
- StudentID (学号): 例如 “1001”。这是一个单一的数值,不能再分割成更小的有意义的部分(比如不能把它分成 ‘1’, ‘0’, ‘0’, ‘1’ 并认为每个都有独立意义)。满足 1NF。
- StudentName (姓名): 例如 “张三”。虽然姓名可以拆分成姓氏和名字(“张” 和 “三”),但在数据库设计中,通常将整个姓名视为一个不可分割的字符串来处理,特别是当我们不需要单独查询姓氏或名字时。如果业务上需要单独处理姓氏和名字,才需要拆分成两列。通常认为满足 1NF。
- StudentDepartment (系别): 例如 “计算机系”。这是一个单一的文本值,表示一个系别。满足 1NF。
- CourseID (课程号): 例如 “CS101”。这是一个单一的标识符,不能再分割。满足 1NF。
- CourseName (课程名): 例如 “数据结构”。这是一个单一的文本值。满足 1NF。
- Professor (教授): 例如 “李教授”。这是一个单一的文本值。满足 1NF。
结论: 在这个例子中,StudentCourse
表的每一列都只包含单一、不可分割的数据项。因此,这个表满足第一范式 (1NF)。
一个不满足 1NF 的例子:
假设我们有一个StudentInfo
表,其中存储了学生的联系电话,但允许一个学生有多个电话:
StudentID | StudentName | PhoneNumbers |
---|---|---|
1001 | 张三 | 13812345678, 13987654321 |
1002 | 李四 | 13700001111 |
1003 | 王五 | 13611112222, 13833334444, 13955556666 |
在这个表中,PhoneNumbers
列包含了多个电话号码,用逗号分隔。这违反了 1NF,因为该列的单元格包含了多个基本数据项(多个电话号码)。
要使其满足 1NF,我们需要将每个电话号码放在单独的一行,或者创建一个单独的 StudentPhone
表。
希望这个例子能帮助你理解第一范式的要求!
好的,我们来通过具体的例子来解释“完全依赖”和“不传递依赖”这两个概念,它们是第二范式(2NF)和第三范式(3NF)的核心。
假设我们有一个存储学生选修课程信息的表:
表:StudentCourse (学生选课表)
StudentID (学号) | StudentName (姓名) | StudentDepartment (系别) | CourseID (课程号) | CourseName (课程名) | Professor (教授) |
---|---|---|---|---|---|
1001 | 张三 | 计算机系 | CS101 | 数据结构 | 李教授 |
1001 | 张三 | 计算机系 | CS201 | 数据库原理 | 王教授 |
1002 | 李四 | 电子系 | EE101 | 电路基础 | 赵教授 |
1003 | 王五 | 计算机系 | CS101 | 数据结构 | 李教授 |
1003 | 王五 | 计算机系 | CS301 | 操作系统 | 刘教授 |
这个表的主键是 (StudentID, CourseID)
,因为一个学生可以选多门课,一门课可以被多个学生选,只有同时知道学号和课程号才能唯一确定一行记录。
2. 第二范式 (2NF) - 完全依赖 (Full Dependency)
定义: 在满足第一范式(1NF)的基础上,非主属性必须完全依赖于整个主键,而不仅仅是主键的一部分。
问题: 如果主键是由多个属性组成的(复合主键),我们需要检查每个非主属性是否依赖于所有主键属性。
在 StudentCourse 表中:
- 主键: (StudentID, CourseID)
- 非主属性: StudentName, StudentDepartment, CourseName, Professor
现在我们检查每个非主属性是否完全依赖于(StudentID, CourseID)
:
- StudentName (姓名): 姓名是由学号
StudentID
决定的,和选了哪门课CourseID
无关。所以StudentName
只依赖于StudentID
,而不是(StudentID, CourseID)
。这叫部分依赖 (Partial Dependency)。 - StudentDepartment (系别): 系别也是由学号
StudentID
决定的,和课程号CourseID
无关。所以StudentDepartment
只依赖于StudentID
,而不是(StudentID, CourseID)
。这也是部分依赖。 - CourseName (课程名): 课程名是由课程号
CourseID
决定的,和学生的学号StudentID
无关。所以CourseName
只依赖于CourseID
,而不是(StudentID, CourseID)
。这同样是部分依赖。 - Professor (教授): 教授通常是由课程号
CourseID
决定的(假设一门课只有一个固定教授),和学生的学号StudentID
无关。所以Professor
只依赖于CourseID
,而不是(StudentID, CourseID)
。这也是部分依赖。
结论: StudentCourse 表不满足 2NF,因为存在非主属性(StudentName, StudentDepartment, CourseName, Professor)部分依赖于主键(StudentID, CourseID)
的子集(要么只依赖StudentID
,要么只依赖CourseID
)。
如何修正 (分解): 我们需要将表分解,使得每个表的非主属性都完全依赖于该表的主键。
- Student (学生表): 主键
StudentID
StudentID (学号) StudentName (姓名) StudentDepartment (系别) 1001 张三 计算机系 1002 李四 电子系 1003 王五 计算机系 - Course (课程表): 主键
CourseID
CourseID (课程号) CourseName (课程名) Professor (教授) CS101 数据结构 李教授 CS201 数据库原理 王教授 EE101 电路基础 赵教授 CS301 操作系统 刘教授 - StudentSC (学生选课关系表): 主键
(StudentID, CourseID)
StudentID (学号) CourseID (课程号) 1001 CS101 1001 CS201 1002 EE101 1003 CS101 1003 CS301
现在检查这些新表:
- Student 表的主键是
StudentID
,所有非主属性 (StudentName, StudentDepartment) 都完全依赖于StudentID
。 - Course 表的主键是
CourseID
,所有非主属性 (CourseName, Professor) 都完全依赖于CourseID
。 - StudentSC 表的主键是
(StudentID, CourseID)
,它没有其他非主属性,所以自然满足。
这些分解后的表都满足了 2NF。
3. 第三范式 (3NF) - 不传递依赖 (No Transitive Dependency)
定义: 在满足第二范式(2NF)的基础上,非主属性必须不传递依赖于主键。也就是说,非主属性不能依赖于另一个非主属性。
问题: 我们需要检查是否存在“主键 -> 非主属性 A -> 非主属性 B”这样的依赖链条,其中 B 就是传递依赖于主键。
在修正后的 Student 表中:
- 主键:
StudentID
- 非主属性: StudentName, StudentDepartment
检查是否存在传递依赖: StudentID
->StudentDepartment
(学号决定系别)StudentDepartment
-> ? 这个系别 (StudentDepartment
) 是否又决定了其他的非主属性?在我们的 Student 表中,系别只决定了它自己,没有决定其他非主属性 (StudentName)。所以没有传递依赖。
在修正后的 Course 表中:- 主键:
CourseID
- 非主属性: CourseName, Professor
检查是否存在传递依赖: CourseID
->Professor
(课程号决定教授)Professor
-> ? 这个教授 (Professor
) 是否又决定了其他的非主属性?在我们的 Course 表中,教授只决定了它自己,没有决定其他非主属性 (CourseName)。所以没有传递依赖。
假设我们有一个不满足 3NF 的表:
表:StudentDept (学生系别表 - 假设大学有系主任)
StudentID (学号) | StudentName (姓名) | StudentDepartment (系别) | DepartmentHead (系主任) |
---|---|---|---|
1001 | 张三 | 计算机系 | 陈主任 |
1002 | 李四 | 电子系 | 吴主任 |
1003 | 王五 | 计算机系 | 陈主任 |
- 主键:
StudentID
(假设学号唯一) - 非主属性: StudentName, StudentDepartment, DepartmentHead
检查依赖关系:
StudentID
->StudentName
(满足)StudentID
->StudentDepartment
(满足)StudentID
->DepartmentHead
? 不完全是。学号本身不直接决定系主任。但是,我们可以通过学号找到系别 (StudentID
->StudentDepartment
),然后通过系别找到系主任 (StudentDepartment
->DepartmentHead
)。所以,DepartmentHead
是传递依赖于主键StudentID
的。StudentID
->StudentDepartment
->DepartmentHead
。
结论: StudentDept 表不满足 3NF,因为存在非主属性DepartmentHead
传递依赖于主键StudentID
。
如何修正 (分解): 我们需要将传递依赖的非主属性分离出来。
- Student (学生表): 主键
StudentID
-
StudentID (学号) StudentName (姓名) StudentDepartment (系别) 1001 张三 计算机系 1002 李四 电子系 1003 王五 计算机系 - Department (系别表): 主键
StudentDepartment
(可以用系别名称或系编号作为主键,这里简化用名称)StudentDepartment (系别) DepartmentHead (系主任) 计算机系 陈主任 电子系 吴主任
现在检查这些新表:
- Student 表满足 3NF。
- Department 表的主键是
StudentDepartment
,唯一的非主属性DepartmentHead
完全依赖于它,并且没有其他非主属性可以依赖,所以也满足 3NF。
总结:
- 完全依赖 (2NF): 非主属性不能只依赖于复合主键的“一部分”。它们必须依赖于整个主键。
- 不传递依赖 (3NF): 非主属性不能依赖于另一个非主属性。它们必须直接依赖于主键,不能通过中间的非主属性间接依赖。
通过满足这些范式,可以减少数据冗余,避免数据更新异常(插入、删除、修改异常)。