从 ER 图到数据库表:用校园故事讲清楚
假设我们要给学校做一个管理系统,需要记录:
- 学校有哪些学生、班级和课程;
- 每个学生在哪个班;
- 每个学生选了哪些课程;
- 每门课的成绩是多少。
这些事情可以用 ER 图表达,也可以用数据库表记录。
我们先认识图里有哪些东西,再学习它们如何转换。
一、先认识 ER 图里的三个主角与关系模式
ER是 Entity–Relationship 的缩写,也就是“实体—联系”。
1. 实体:我们要记录的对象
学生、班级、课程,都可以是实体。
更准确地说,“学生”是一类对象,张小明、李小红是其中一个个具体对象。画图时,我们通常画出“学生”这一类。
传统 ER 图一般用矩形表示实体。
2. 属性:用来描述对象的信息
例如:
| 实体 | 属性 |
|---|---|
| 学生 | 学号、姓名、出生日期 |
| 班级 | 班级编号、班级名称 |
| 课程 | 课程编号、课程名称、学分 |
传统 ER 图一般用椭圆表示属性。
其中,有些属性能帮助我们准确找到某一个对象。比如姓名可能重复,学号通常不能重复,因此学号适合用作学生的标识。
3. 联系:对象之间的关联
例如:
- 学生“属于”班级;
- 学生“选修”课程;
- 教师“教授”课程。
传统 ER 图一般用菱形表示联系。
不过,不同工具的画法不完全相同。有些图直接用连线和符号表示联系。判断图的含义,要看图例和标记,不能只看形状。
关系模式
关系模式,就是一张数据库表的“结构说明”:这张表叫什么、包含哪些字段,以及这些字段需要遵守哪些规则。
比如,我们要记录学生信息,可以写成:
学生(学号,姓名,出生日期,班级编号)这就是一个关系模式。其中:
- 学生:关系名,可以理解为表名。
- 学号、姓名、出生日期、班级编号:属性,可以理解为列名,也叫字段名。
二、转换前最重要的一步:判断“能对应几个”
看到两个实体有联系,先从两个方向各问一次:
对于这边的一个对象,另一边最多可以有几个?
这样就能区分三种基本联系。
| 联系类型 | 校园中的例子 | 判断方式 |
|---|---|---|
| 一对一,1∶1 | 学生与校园账户,假设每人最多一个账户且账户不共用 | 一个学生最多一个账户,一个账户最多属于一个学生 |
| 一对多,1∶N | 班级与学生 | 一个班有多个学生,一个学生只属于一个班 |
| 多对多,M∶N | 学生与课程 | 一个学生可选多门课,一门课可被多个学生选择 |
这里的规则来自业务约定。
例如,如果学校允许一个学生同时属于多个教学班,“班级与学生”的联系就需要重新判断。
还要补问一个问题:
能不能一个也没有?
“最多几个”和“最少几个”是两件事。例如:
- 一个学生可以暂时不选课:对应课程数可以是
0..N; - 每个学生必须属于一个班:对应班级数是
1..1。
这决定了联系是否必须存在,也会影响外键能不能留空。
三、认识关系模式里的四个词
关系数据库用表来组织数据。先记住:
| 术语 | 简单理解 |
|---|---|
| 表 | 记录一类信息,例如学生表 |
| 行 | 一条具体记录,例如某个学生 |
列(字段) | 一项信息,例如姓名 |
键 | 字段或字段的组合:主键用于唯一标识本表中的记录; 外键用于引用本表或其他表中的记录,建立关联。 |
其中,最重要的是主键和外键。
**主键(PK)**用于唯一识别一行。例如,学生表用“学号”作主键。主键不能重复,也不能为NULL;有时需要几个字段一起组成主键。
**外键(FK)**用于引用其他表中的记录。例如,学生表里的“班级编号”可以引用班级表里的“班级编号”。
可以这样记:
主键回答“这一行是谁”,外键回答“这一行关联谁”。
严格来说,外键也可以引用满足唯一性要求的其他键。
入门时先掌握引用主键的常见情况即可。
四、从 ER 图变成关系模式:四条基本规则
规则一:普通实体通常变成一张表
例如,学生实体可以转换为:
学生(学号,姓名,出生日期) 主键:学号班级实体可以转换为:
班级(班级编号,班级名称) 主键:班级编号入门时,可以先记住“实体变表,简单属性变列”。
多值属性等特殊情况,后面再单独处理。
规则二:一对多,把“一方”的主键放到“多方”
一个班级对应多个学生,所以把班级编号放进学生表:
班级(班级编号,班级名称) 主键:班级编号 学生(学号,姓名,班级编号) 主键:学号 外键:班级编号 → 班级.班级编号例如:
班级表
| 班级编号 | 班级名称 |
|---|---|
| B01 | 七年级一班 |
| B02 | 七年级二班 |
学生表
| 学号 | 姓名 | 班级编号 |
|---|---|---|
| S01 | 小明 | B01 |
| S02 | 小红 | B01 |
| S03 | 小林 | B02 |
学生表里的B01可以重复,因为同一个班里本来就有多个学生。
记忆方法:
一对多,外键放在“多”的一边。
规则三:多对多,通常需要增加一张中间表
一个学生能选多门课,一门课也能被多个学生选择,因此建立“选课表”:
学生(学号,姓名) 主键:学号 课程(课程编号,课程名称) 主键:课程编号 选课(学号,课程编号,成绩) 主键:(学号,课程编号) 外键:学号 → 学生.学号 外键:课程编号 → 课程.课程编号选课表
| 学号(字段) | 课程编号 | 成绩 |
|---|---|---|
| S01(字段值) | C01 | 92 |
| S01(字段值) | C02 | 88 |
| S02(字段值) | C01 | 95 |
这里,每一行表示“一名学生选了一门课”。
为什么成绩放在选课表里?因为:
- 只知道学生,无法确定是哪门课的成绩;
- 只知道课程,也无法确定是谁的成绩;
- 同时知道学生和课程,才能确定这次选课对应的成绩。
这说明:联系本身也可以有属性。
上述主键有一个前提:
每名学生对每门课程只记录一次选课。
如果允许不同学期重修,就需要加入学期、开课编号等信息,重新设计唯一标识。
记忆方法:
多对多,中间建表;联系的属性,跟着联系走。
规则四:一对一,可以用“外键+唯一约束”
假设每个学生最多一个校园账户,每个账户也只属于一个学生:
学生(学号,姓名) 主键:学号 校园账户(账户编号,学号,账户名称) 主键:账户编号 外键:学号 → 学生.学号 唯一约束:学号不能重复 非空约束:学号不能为 NULL为什么要给学号加唯一约束?
因为只有外键时,同一个学号仍然可能出现在多个账户记录里;
加上唯一约束,才能限制“一名学生最多一个账户”。
这个设计允许某个学生暂时没有账户,但要求每个已有账户必须属于一名学生。
要保证“每名学生都必须有账户”,还需要进一步的约束或业务流程。
记忆方法:
一对一,外键还要唯一;
是否必须存在,要另外检查。
五、从关系模式反推 ER 图:看表,更要看约束
反向转换时,可以按下面的顺序观察。
1. 找出表示对象的表
学生表、班级表、课程表,通常可以还原成对应实体。
但不要机械地认为“一张表一定是一个实体”,因为中间表可能代表的是联系。
2. 沿着外键找联系
例如:
学生.班级编号 → 班级.班级编号说明学生与班级之间存在联系。
3. 检查外键有没有唯一约束
这是判断一对多和一对一的关键。
| 表中约束 | 通常可以推断的含义 |
|---|---|
| 外键允许重复 | 多行可以关联同一条被引用记录,对应一对多结构 |
| 外键有唯一约束 | 最多一行关联同一条被引用记录,对应一对一结构 |
外键允许NULL | 这一行可以暂时不关联被引用对象 |
外键不允许NULL | 这一行必须关联一个被引用对象 |
注意:外键不为空,只能保证引用方必须有对应对象。例如,每个学生必须有班级,并不能因此推出每个班级都必须有学生。
4. 找出连接两个实体的中间表
例如:
选课(学号,课程编号,成绩)如果学号和课程编号都是外键,而且它们的组合具有唯一性,通常可以还原为:
学生与课程是多对多联系,“成绩”是这个联系的属性。
如果中间表还有独立身份和复杂业务,例如订单、报名记录,也可以把它建模为一个关联实体。
只看几行样例数据,不能可靠判断联系类型。当前每个班恰好只有一个学生,不代表班级与学生就是一对一。应结合主键、外键、唯一约束和业务规则判断。
六、知识补充
1. 一个格子里不要塞一串编号(违反第一范式)
下面的设计不适合常规关系数据库的入门建模:
| 学号 | 所选课程 |
|---|---|
| S01 | C01、C02、C03 |
这样很难检查课程是否存在,也不方便逐门统计成绩。
更合适的做法是建立选课表,一行记录一次选课。
这也有助于理解第一范式:每个字段应保存符合其定义的单个值,不用一个字段存放需要分别查询和关联的一组编号。
2.NULL不等于 0,也不等于空字符串
成绩为0,表示成绩就是零分;
成绩为NULL,可能表示还没有录入。
具体含义要由业务规则明确,不能随意混用。
3. 同一份事实,尽量在合适的位置保存一次
如果每个学生的记录里都写一遍班级名称,班级改名时就要修改很多行,还容易漏改。
把班级名称保存在班级表里,学生表只保存班级编号,就能减少重复和修改不一致的问题。这是理解规范化的一个入口。
4. 有些属性需要特殊处理
- 复合属性:例如地址,可以按查询需要拆为省、市、详细地址。
- 多值属性:例如一个学生有多个联系电话,通常另建联系电话表。
- 派生属性:例如年龄,可以根据出生日期和当前日期计算,通常优先保存出生日期。
5. 有些对象必须依靠另一个对象来识别
例如“订单明细”的明细序号只在某张订单内部唯一,就可以用“订单编号+明细序号”一起识别。
这是理解弱实体的常见例子,可以放在进阶部分讲。
6. 对于“码”的一些理解
- 码(候选码):能唯一标识本表记录的最小属性集合。
- 外码(外键):表中用于引用某个关系的候选码的属性或属性组合, 不要求在本表中唯一。
- 码通常指候选码;主码是选中的候选码;
- 外码则是另一个专门概念。
- 超码,就是能够唯一标识一条记录的属性或属性组合,允许包含多余属性。
7. 联系属性的放法
| 联系类型 | 联系属性通常放哪里 |
|---|---|
| 一对多 | 放在多方表中,与外键放在一起 |
| 多对多 | 放在中间表中 |
| 一对一 | 放在保存外键的表中 |
| 联系需要保存多次发生的历史 | 通常单独建立记录表 |
最关键的一点:属性在概念上属于“联系”,不代表转换成表时就一定要单独建一张表。
只要那张表能准确记录这段联系及其属性,就可以。
七、一张表记住互转方法
| ER 图中的结构 | 常见关系模式实现 | 反向识别时看什么 |
|---|---|---|
| 普通实体 | 一张表 | 表是否表示独立的业务对象 |
| 简单属性 | 一列 | 列的含义和数据类型 |
| 标识属性 | 主键或候选键 | 主键、唯一与非空约束 |
| 一对多联系 | 多方保存一方的外键 | 外键通常允许重复 |
| 一对一联系 | 外键加唯一约束,或共享主键 | 外键是否唯一 |
| 多对多联系 | 建立中间表 | 两端外键及其组合的唯一性 |
| 联系的属性 | 放在记录该联系的表中 | 属性描述的是对象,还是对象间的关联 |
八、用三个问题检查自己是否真的会了
问题一:一个班有很多学生,班级编号放哪张表?
放在学生表,因为学生是“多”的一方。
问题二:学生可以参加多个社团,一个社团也有多个学生,怎么办?
多对多,建立“参加社团表”,记录学号、社团编号,以及加入日期等联系属性。
问题三:校园账户表里已经有学号外键,就能保证每人最多一个账户吗?
不能。还需要让学号具有唯一性,否则多个账户仍然可以引用同一个学生。