目录:

一、关系型数据库设计范式

范式:Normal Format,符合某一种级别的关系模式的集合,表示一个关系内部各属性之间的联系的合理化程度

范式目标是在满足组织和存储的前提下使数据结构冗余最小化,用来减少数据冗余

范式级别越高,表的级别就越标准

各种范式之间的关系:$5NF \subset 4NF \subset BCNF \subset 3NF \subset 2NF \subset 1NF$

1、第一范式 1NF

第一范式:1NF,关系中每一分量不可再分。数据字段设计时必须满足原子性

满足1NF 的关系称为规范化的关系

如果数据使用的时候需要进行拆分那么就违背1NF

1NF是满足数据表设计的最基础规范

示例

设计一张学生选修课成绩表

学生 性别 课程 教室 成绩 学习时间
张三 PHP 101 100 2月1日,2月28日
李四 Java 102 90 3月1日,3月31日
张三 Java 102 95 3月1日,3月31日

当前表的学习时间在使用的时候肯定是基于开始时间和结束时间的,而这种设计就会存在使用时的数据拆分,不满足原子性也就是1NF

满足1NF的设计:字段颗粒度应用层最小(不需要拆分)

学生 性别 课程 教室 成绩 开始时间 结束时间
张三 PHP 101 100 2月1日 2月28日
李四 Java 102 90 3月1日 3月31日
张三 Java 102 95 3月1日 3月31日

2、第二范式 2NF

第二范式:若R∈1NF,且每个非主属性完全函数依赖于码,则称R∈2NF

2NF的定义要求消除非主属性对码的部分依赖。

  • 部分依赖:首先表存在复合主键,其次有的字段不是依赖整个主键,而只是依赖主键中的一部分
  • 部分依赖解决:让所有非主属性都依赖一个候选关键字
    • 最简单方式:取消复合主键(一般选用逻辑主键替代,但是本质依然是复合主键做主),所有非主属性都依赖主属性(逻辑主键)
    • 正确方式:将部分依赖关系独立成表

示例

学生成绩表中学生和课程应该是决定性关系,因此属于主属性(主键)

学生(P) 性别 课程(P) 教室 成绩 开始时间 结束时间
张三 PHP 101 100 2月1日 2月28日
李四 Java 102 90 3月1日 3月31日
张三 Java 102 95 3月1日 3月31日
  • 成绩是由学生和课程决定的,是完全依赖主属性
  • 性别只依赖学生(部分依赖)
  • 教室、开始时间和结束时间依赖课程(部分依赖)

解决方案:将学生信息维护到一张表,课程信息维护到一张表,成绩表取两个表的主属性即可

学生表

Stu_id(P) 姓名 性别
1 张三
2 李四
  • Stu_id是姓名的代指属性(逻辑主键,本质主键是姓名)
  • 性别只依赖主属性

课程表

Class_id(P) 课程 教室 开始时间 结束时间
1 PHP 101 2月1日 2月28日
2 Java 102 3月1日 3月31日
  • Class_id是课程的代指属性(逻辑主键)
  • 教室、开始时间和结束时间都依赖课程(主属性)

成绩表

Stu_id(P) Class_id(P) 成绩
1 1 100
2 2 90
1 2 95
  • Stu_id和Class_id共同组成主属性(复合主键)
  • 成绩依赖Stu_id和Class_id本身,不存在部分依赖

3、第三范式 3NF

第三范式:3NF,关系模式 $R<U,F>$ 中,若不存在这样的码 $X$,属性组 $Y$ 及非主属性 $Z(Z \nsubseteq Y)$,使得 $X \to Y(Y \nrightarrow X), Y \to Z$ 成立

3NF的定义要求消除非主属性对码的传递依赖

  • 传递依赖:字段某个非主属性不直接依赖主属性,而是通过依赖某个其他非主属性而传递到主属性之上
  • 传递依赖解决:让依赖非主属性的字段与依赖字段独立成表

示例

学生表:包括所在系信息

学号(P) 姓名 专业编号 专业名字
1 张三 0001001 软件工程
2 李四 0001002 土木工程
  • 姓名和专业编号都依赖于学号(为学号提供信息支持)
  • 专业名字依赖专业编号(为编号提供信息支持)
  • 专业名字间接依赖学号:传递依赖
  • 随着学生增加,专业名字会出现大量数据冗余

解决方案:将存储传递依赖部分的字段(非主属性)独立成表,然后在需要使用相关信息的时候,引入即可

专业表

专业编号(P) 专业名字
0001001 软件工程
0001002 土木工程
  • 即使有更多的信息为专业提供支持也不存在传递关系

学生表

学号(P) 姓名 专业编号
1 张三 0001001
2 李四 0001002
  • 姓名和专业编号都依赖学号(为学号提供信息支持)
  • 没有其他字段是通过非主属性(专业编号)来依赖主属性的:没有传递依赖
  • 学生再多,专业名字信息只需要维护一次,减少数据冗余

4、BCNF

BCNF:关系模式 $R<U,F> \in 1NF$ ,对于属性组$X$ 和 $Y$,若 $X \to Y$ 且 $Y \nsubseteq X$ 必含有码,则 $R<U,F> \in BCNF$

所有非主属性对每一个码都是完全函数依赖

所有的主属性对每一个不包含它的码,也是完全函数依赖

没有任何属性完全函数依赖于非码的任何一组属性

示例

仓库管理关系表:满足一个管理员只在一个仓库工作;一个仓库可以存储多种物品

仓库 管理员 物品名 数量
上海 李华 Huawei 100
上海 李华 iPhone 50
北京 小明 Huawei 70
北京 小明 iPhone 50

(仓库,物品) 和 (管理员,物品) 都是仓库管理关系表的候选码,表中唯一非关键字段为数量,此关系模式已经属于了 3NF,但还存在以下问题:

  • 插入异常:新增加一个仓库,但尚未存放任何物品,无法为该仓库指派管理员——物品名也是主属性,根据实体完整性的要求,主属性不能为空。
  • 删除异常:某仓库被清空后,需要删除所有与这个仓库相关的物品存放记录——仓库本身与管理员的信息也被随之删除。
  • 更新异常:如果某仓库更换了管理员,这个仓库有几条物品存放记录,就要修改多少次管理员信息。

解决方案:把仓库管理关系表分解为两个关系表仓库管理表(仓库,管理员)和仓库表(仓库,物品,数量)

仓库管理表

仓库 管理员
上海 李华
北京 小明

仓库表

仓库 物品 数量
上海 Huawei 100
上海 iPhone 50
北京 Huawei 70
北京 iPhone 50

5、逆规范化

逆规范化:为了提升数据查询的效率刻意违背范式的规则

逆规范化的目标是为了提升数据访问效率

所谓逆规范化就是减少表之间的关联查询(效率降低),刻意增加数据冗余

示例

1、学生成绩表需要经常查询,而且数据量很大,但是:

  • 成绩表中只有学号,显示的时候需要学生姓名(去学生表中连表查询)
  • 成表表中只有课程号,显示的时候需要显示课程名(去课程表中连表查询)
  • 逆规范化:将学生姓名和课程名在表中冗余维护(不满足2NF)
学号(P) 学生姓名 课程号(P) 课程名字 成绩
1 张三 1 PHP 100
1 张三 2 Java 90
  • 学生姓名部分依赖学号(主属性):不满足2NF
  • 学生姓名和课程名字会有大量数据冗余存在(不满足2NF导致)

二、表关系

  • 一对一关系
  • 一对多关系(多对一)
  • 多对多关系

1、一对一关系

一对一关系:一张表中的一条记录与另外一张表中有且仅有一条记录有关系

一对一关系通常是用来将一张原本就是一体的表拆分成两张表

  • 频繁使用部分:常用字段
  • 不常使用部分:生僻字段
  • 使用相同的主键对应

一对一关系设计较多使用在优化方面,因为一对一关系表通常有相同信息作为匹配条件,所以查询方式也比较方便

  • 连表操作:利用共有信息进行匹配,一并查出一条完整信息
  • 多次查询:利用共有信息进行多表查询,利用程序组合成一条完整信息

示例

学生信息表

学号(P) 姓名 性别 年龄 身高 体重 籍贯 政治面貌
1 张飞 20 178 160 农民
2 武则天 21 168 110 党员
  • 以上数据表信息字段较多
  • 姓名、性别、年龄属于常用字段,频繁查询

2、一对一关系设计

  • 将常用字段取出,与学号组合成一张常用表
  • 将不常用字段取出,与学号组合成一张不常用表
  • 表与表数据对应关系:基于学号(唯一)是一对一关系

常用表

学号(P) 姓名 性别 年龄
1 张飞 20
2 武则天 21

不常用表

学号(P) 身高 体重 籍贯 政治面貌
1 178 160 农民
2 168 110 党员

2、一对多关系

一对多关系:也叫多对一关系,一张表中的一条记录与另外一张表的多条记录对应,反过来另外一张表的多条记录只能对应当前表的一条记录

一对多关系是实体中非常常见的一种关系,实体设计时也应用非常多

一对多关系的核心解决方案是如何让记录能够正确匹配到另外表中的数据

  • 一表设计:一表记录在另外一张表中有多条记录,所以无法记录多个字段(违背1NF)
  • 多表设计:多表记录在另外一张表中只有一条记录,可以设置字段记录对应的主属性(通常主键)

示例

1、老师与学科间的关系:一个老师只能教一个学科,但是一个学科有多个老师教授,学科与老师形成的关系就是一对多(反过来老师与学科的关系就是多对一关系)

老师表(多表)

老师ID(P) 姓名 年龄 性别
1 张老师 35
2 李老师 34
3 王老师 30

学科表(一表)

学科ID(P) 名字 课时长度
1 PHP 600
2 Java 800
  • 以上两个实体没有体现彼此之间的关联关系
  • 实际上讲师与学科肯定是有关联的

2、在多表(讲师)中增加字段维护一表(学科)的关系型,形成多对一关系

老师ID(P) 姓名 年龄 性别 学科ID
1 张三 35 1
2 李四 34 1
3 王五 30 2
  • 基于新的讲师表与学科表产生了关联关系(多对一)
  • 基于讲师表可以知道讲师所属学科
  • 基于学科ID可以统计出不同学科的讲师数量

3、多对多关系

多对多关系:一张表中的一条记录对应另外一个表中多条记录,反过来一样

多对多关系在实体中是最常见的关系

多对多关系是无法在自身表中维护对应表关系的(违背1NF),需要通过第三方表来实现将多对多关系变成多个多对一关系

  • 设计一个中间表:记录两张表之间的对应关系(主属性)
  • 中间表与其他表都是多对一的关系

示例

1、老师与学生之间的关系:一个老师会教授多个学生,一个学生也会听多个老师的课,所以实体关系是多对多关系

老师表

老师ID(P) 姓名 年龄 性别
1 张老师 35
2 李老师 34
3 王老师 30

学生表

学生ID(P) 姓名 年龄 性别
1 小明 15
2 小红 14
3 小萌 14
  • 以上实体没有从结构上体现表之间的关系

2、设计一个中间表:老师与学生关系表,将老师与学生的对应关系对应上(多对一)

中间表

ID(P) 学生ID 老师ID
1 1 1
2 1 2
3 1 3
4 2 1
5 2 2
6 2 3
7 3 1
8 3 3
  • 中间表与老师表的对应关系是多对一:通过老师ID可以找到每一个上过课的老师
  • 中间表与学生表的对应关系是多对一:通过学生ID可以找到每一个听过课的学生
  • 老师找学生:老师表 → 中间表(找出老师对应的学生ID) → 学生表(找出学生ID对应的学生信息)
  • 学生找老师:学生表 → 中间表(找出学生对应的老师ID) → 老师表(找出老师ID对应的老师信息)