关系模式范式浅谈
前言
经验很重要,不论是自己的还是别人的,遵循好的经验行事,可以达到事半功倍的效果,因此不断的总结与学习经验,对于自身能力的提高有很大的帮助。
好的经验可以做为行事的准则,可以通过判断行为是否符合准则,以判断行为的优劣, 《春秋》也有说“欲知平直,则必准绳,欲知方圆,则必规矩”。在软件设计领域,将这些经验称之为设计模式,而在关系数据库领域,则将这些经验称之为范式(关系模型满足特定条件的约束称之为范式),因此在数据库设计中应当遵循范式,以使关系模式能适应各种复杂的业务场景。本文将介绍关系模型范式在关系模型设计中的应用。
背景
为方便讨论,本文基于一个具体案例讨论:某学校需开发一套学生管理系统,需记录学生的成绩,登记的信息有 学号, 身份证号,姓名,家庭地址, 课程名称,所属系,系主任,课程分数。使用关系数据库来存储成绩信息。
新手上路
若仅需满足以上项目需求,不考虑可扩展性的前提下,使用一张关系表即可,如下所示:
学号 | 身份证号 | 姓名 | 家庭地址 | 课程名称 | 所属系 | 系主任 | 课程分数 |
0001 | XXXXX01 | 张三 | 浙江省杭州市 | 计算机 | 计算系 | 李四 | 90 |
0001 | XXXXX01 | 张三 | 浙江省杭州市 | 人工智能 | 计算系 | 李四 | 95 |
|
|
|
|
|
|
|
|
关系表Student
将需要登记的信息全部放到一张关系表中。但是这是一个好的设计方案吗?前面提到关系模式的范式是判断设计优劣的准则,常用的范式有:
l 第一范式1NF:要求所有的属性必须不可再拆分
l 第二范式2NF:满足第一范式前提下要求所有的非主属性必须完全依赖于主属性
l 第三范式3NF:满足第二范式前提下要求所有的非主属性不传递依赖于主属性
l BC范式:满足第三范式前提下要求所有的主属性不依赖于其它主属性
(术语:关系R, 如果某属性或一组属性确定,那么其它的几个属性也随之确定,那么这几个属性统称即为码, 码中的属性为主属性,一个关系中可能存在多个码,不存在于任何码中的属性称之为非主属性,
部分依赖:若码中的部分属性即可确定其它非主属性,则称之为部分依赖,否则为完全依赖。
)
关系表Studentl连第一范式也不满足!那么有什么问题呢?
查询
随着项目需求的变化,现在需要统计学生的分布情况,显然该关系模型不能优雅的实现该功能, 若使用like语法统计,则要求家庭地址需严格按正确格式录入,并且还需要去重复操作。
第一范式提供了好的设计指导,根据第一范式要求,所有的属性均需不可再次拆分,将家庭地址拆分成省,市等,可得到以下表结构。
学号 | 身份证号 | 姓名 | 省 | 市 | 课程名称 | 所属系 | 系主任 | 课程分数 |
0001 | XXXXX01 | 张三 | 浙江 | 杭州 | 计算机 | 计算系 | 李四 | 90 |
0001 | XXXXX01 | 张三 | 浙江 | 杭州 | 人工智能 | 计算系 | 李四 | 95 |
|
|
|
|
|
|
|
|
|
关系表student2
关系表student2对查询统计的支持比关系表student的支持就好多了。只需通过选择统计省及学号字段,去除重复项即可。
第一范式是关系模型设计中应遵循的最基础的范式,要求所有的数据属性并不可再拆分,但在实际使用时还是需要根据具体的业务需求分析,比如以上例子,如果项目需求中并永远不会出现统计学生分布,则不需要将家庭地址拆分,否则就会出现过度设计的问题,事实上这种属性是否需要再细分很难判断,需要有相当的前瞻性,比如是否需要按学生姓氏统计呢?真的很难讲。
更新
数据冗余会带来很多问题,比如在数据更新方面,为保持数据一致,往往需要做出更多的操作,而关系模型的范式正是通过消除函数依赖性来达到减少数据冗余的问题,遵循范式可以很好的消除数据冗余。
前面提到范式2要求所有非主要属性必须完全依赖于主要属性,即当主要属生确定时,非主要属性即确认,(主要属性组成的属性组称之为码,若有多个码,则选一个码为主码/主键),范式2能消除一部分的冗余,范式3在范式2的基础上更进一步,要求非主要属性不传递依赖于主要属生,BC范式则在范式3上又更新一步。在实际项目中,关系模型设计需满足第几范式往往与实际业务相关,并不是满足更严格的范式就越好,适用即可。
现在来看一下关系表student2是否能适应后续项目需求,项目开展,现在引入另一个需求,即修改学生家庭地址,从关系表student2可知,学生信息冗余地存储在关系表中,当需要修改学生信息时,则必须修改所有的相关行,造成这一现象的原因就是数据冗余存储。
关系表student2并不能满足范式2, 该表中码为(学号,课程名称) 但是身份证号, 省,市 等部分依赖于码(学号属性) , 所属系, 系主任 部分依赖于码(课程名称)
根据范式2要求,可以拆分该关系表student2为以下表
学号 | 身份证 | 姓名 | 省 | 市 |
0001 | XXXXX01 | 张三 | 浙江 | 杭州 |
|
|
|
|
|
关系表studentInfo
课程 | 所属系 | 系主任 |
计算机 | 计算系 | 李四 |
人工智能 | 计算系 | 李四 |
关系表class
学号 | 课程 | 分数 |
0001 | 计算机 | 90 |
0001 | 人工智能 | 95 |
关系表studentClassScore
PS 红色加粗字体为主码, 加粗字体为主要属性,为简单起见,假设每个系的课程名不得重复。
经过重新设计,以上关系模型已经满足第二范式,这时再修改学生地址信息,只需要修改关系表studentInfo中的省,市字段即可,该设计方案比之前的关系表student1 在修改学生家庭地址信息时有所改进,
到此虽然满足了第二范式,但是如果要修改省名,市名或者系主任,仍会现存在需要修改多行数据的现象,为解决这种现象可以进一步遵循范式3, 消除传递依赖。
对于关系表studentInfo,存在依赖链: 学号->身份证->省->市
对于关系表class存在依赖链: 课程->所属系->系主任
根据第3范式,对关系表class可以进一步拆分为
课程 | 系ID |
计算机 | 001 |
人工智能 | 001 |
系ID | 系主任 |
001 | 李四 |
|
|
这样设计,在更新系主任信息时,即可只修改一行数据即可,关系表studentInfo的传递依赖也可以重新设计成以下格式:
学号 | 身份证 | 姓名 | 省ID | 市ID |
0001 | XXXXX01 | 张三 | 0001 | 0001 |
|
|
|
|
|
关系表studentInfoV001
省ID | 省名 |
0001 | 杭州 |
|
|
市ID | 市名 | 所属省ID |
0001 | XXXXX01 | 0001 |
|
|
|
这样关系表studentInfo就符合第3范式,数据冗余在很大程度上被大大消余了,但关系模型的复杂性也上提高了,在设计关系模型时需要满足至哪一级的范式,还里需要根据需求决定,以免过度设计。
另外关系表studentInfoV001其实并不符合BC范式,该范式要求主要属性不依赖于主要属性,但是在关系表studentInfoV001中 身份证依赖于学号,如果要满足BC范式的话,可设计为
学号 | 身份证 |
0001 | XXXXX01 |
|
|
身份证 | 姓名 | 省ID | 市ID |
XXXXX01 | 张三 | 0001 | 0001 |
|
|
|
|
总结
关系模型范式通过消除函数依赖性来消除数据冗余,提供了判断关系模型设计优劣的依据,但是在实际项目中,还是需要根据实际情况分析,遵循合适的范式即可,范式等级越高,关系模型越复杂,软件开发及维护成本也就越高,但可扩展性也越大,对于小项目来说,投入与产出可能不成正比。