数据库中的表在设计时,为了使数据的冗余较小、接口合理,通常需要遵循一定的原则。在关系型数据库中,这些原则就叫做范式。实际中,最常见的范式主要有三种,本文对三种范式进行了简单的说明。
需要注意的是,完全遵守范式设计出来的数据库,通常来说能够将数据冗余降到最低,能够提高数据库在写时的性能,但同时,在很多情况下,完全范式的数据库在查询方面会比较复杂。
完全范式化与完全反范式化的优缺点分别如下:
完全范式化 | 完全反范式化 | |
优点 |
|
|
缺点 |
|
|
因此,在设计数据库时,无论是完全范式化还是完全反范式化可能都不是一个好的选择。在实际中,通常会根据实际情况,在设计数据库时采用部分范式化的设计。
第一范式
列不可分(列的原子性):每一列的数据都是一个原子项,不能分裂成几列。
例子:如下表中的联系方式字段,有的存储的是手机号码,有的存储的是电子邮箱,有的既存储了手机号码,又存储了邮箱,这是不规范的,破坏了列的原子性。
学生ID | 姓名 | 联系方式 |
1 | 张三 | 13812345678 |
2 | 李四 | 18745678910,lm@qq.com |
3 | 王五 | 123456@qq.com |
正确做法是将其改为两列,使手机号码和电子邮箱各自为一列,如下。
学生ID | 姓名 | 手机号码 | 电子邮箱 |
1 | 张三 | 13812345678 | null |
2 | 李四 | 18745678910 | lm@qq.com |
3 | 王五 | null | 123456@qq.com |
第二范式
行有主键,且如果主键是由多个列组合而成的,则非主键的列必须依赖于主键这个组合整体,不能是只依赖主键中的某一列。
也就是说,在有的表中,主键是联合主键的情况下,所有非主键的列必须依赖整个联合主键,不能只依赖于联合主键中的某一个部分。
例子:下表中,课程和学生是多对多关系,因此,由课程ID和学生ID组成联合主键来唯一的确定一列。
此外,学生的成绩与此联合主键一一对应,不是单独依赖于学生ID和课程ID,因此是完全依赖于主键。然而,姓名与课程名称只是部分依赖主键,即姓名只依赖与学生ID,而课程主键只依赖于课程ID。因此,违反了第二范式。
课程ID | 学生ID | 姓名 | 课程名称 | 成绩 |
1 | 1 | 张三 | 数据结构 | 88 |
2 | 1 | 张三 | 算法分析 | 92 |
1 | 2 | 李四 | 数据结构 | 73 |
2 | 2 | 李四 | 算法分析 | 89 |
1 | 3 | 王五 | 数据结构 | 64 |
2 | 3 | 王五 | 算法分析 | 83 |
这样做的弊端在于,假设需要向数据库中新增加一门课程(或者学生),就必须要有学生选了该课程才能成功存储,因为此时才具有了主键。
正确的设计应该是将其拆分为三张表,如下所示。
课程ID | 学生ID | 成绩 |
1 | 1 | 88 |
2 | 1 | 92 |
1 | 2 | 73 |
2 | 2 | 89 |
1 | 3 | 64 |
2 | 3 | 83 |
学生ID | 姓名 |
1 | 张三 |
2 | 李四 |
3 | 王五 |
课程ID | 课程名称 |
1 | 数据结构 |
2 | 算法分析 |
第三范式
直接依赖,即所有非主键的列必须直接依赖于主键,而不能存在间接依赖。
例子:表中用户ID依赖于订单号,而姓名依赖于用户ID而非直接依赖于订单号,这样就出现了冗余数据。
订单号 | 用户ID | 姓名 |
1 | 1 | 张三 |
2 | 1 | 张三 |
3 | 2 | 李四 |
4 | 2 | 李四 |
5 | 3 | 王五 |
正确的做法同样是将其拆分为两张表,如下:
订单号 | 用户ID |
1 | 1 |
2 | 1 |
3 | 2 |
4 | 2 |
5 | 3 |
用户ID | 姓名 |
1 | 张三 |
2 | 李四 |
3 | 王五 |