数据库设计的第一步从来不是建表,是跟业务方聊天。上一章的范式负责「拆得对」,但「该有哪些表」这个问题,范式一个字都帮不上:它只验收,不创作。创作阶段的主流工具就是 ER 模型(实体-联系图):先用图形把「有哪些东西、它们怎么关联」画清楚,谈的对象是业务里的名词和动词,用户能看懂、能纠正,确认之后再机械地翻成关系模式。本章就走一遍完整流程,拿一个真实的课程设计需求(图书借阅系统)从需求文档一路做到建表语句。
需求文档(业务方原话,故意保持含糊):「读者可以借书,一本书可以被很多人借过;每次借阅要记谁借的、什么时候、还了没;图书有多个副本,同书名的书馆里可能有 10 本;读者分学生和教师,借阅上限不同。」
从这段话里挖 ER 的三样东西:
画成 ER 图(教材符号:矩形实体、菱形联系、椭圆属性,下划线标码属性):
读者 ──────◇借阅◇────── 副本 ──────◇属于◇────── 图书
(读者号) 借出日/归还日 (书号+副本号) (书号,书名)
姓名 状态
类型
两个画图心得,都是被退回重画换来的。其一,联系是动作不是名词:「读者拥有借书卡」如果画成「借书卡」实体,后面会发现借书卡和读者 1:1 且属性完全冗余,基本是画蛇添足。其二,属性挂错地方最常见:「借出日期」挂到副本上,副本的历史借阅记录就没地方放了,挂到联系上才对,判断标准是「这个描述描述的是一次关联,还是实体本身」。
联系两端的数量关系,决定翻表时怎么处理:
判断时拿两个方向各问一遍:「一个读者能借几个副本?多个。一个副本能被几个读者借过?多个。」两问都是多个,M:N。这种双向提问的习惯能挡掉八成设计错误,特别是在「1:N 还是 M:N」摇摆的场合(比如「部门-员工」要不要支持一人多部门,业务答完再定,别替业务拍板)。
确认 ER 图后,翻译是纯体力活,规则四条:
图书系统落出来就是:
CREATE TABLE reader (
rid TEXT PRIMARY KEY,
rname TEXT NOT NULL,
rtype TEXT CHECK (rtype IN ('学生','教师'))
);
CREATE TABLE book (
bid TEXT PRIMARY KEY,
btitle TEXT NOT NULL
);
CREATE TABLE copy (
bid TEXT,
copy_no INTEGER,
status TEXT DEFAULT '在馆',
PRIMARY KEY (bid, copy_no),
FOREIGN KEY (bid) REFERENCES book(bid)
);
CREATE TABLE borrow (
rid TEXT,
bid TEXT,
copy_no INTEGER,
out_date DATE NOT NULL,
in_date DATE,
PRIMARY KEY (rid, bid, copy_no, out_date), -- 同一读者同一副本可多次借,加日期
FOREIGN KEY (rid) REFERENCES reader(rid),
FOREIGN KEY (bid, copy_no) REFERENCES copy(bid, copy_no)
);
翻完再用第 5 章的范式逐表验收一遍(本例各表已无部分/传递依赖),合格才进第 9 章的物理设计(索引、分区)。这条流水线,概念(ER)→ 逻辑(关系模式 + 范式)→ 物理(索引存储),正是第 1 章三级模式思想在设计流程上的展开。
副本其实是个「弱实体」:它自己不足以被识别,「1 号副本」只有加上「哪本书的」才有意义。教材用双矩形表示,规则是:弱实体的主码 = 自己的区分号 + 所属实体的主码(copy 表的主码 (bid, copy_no) 正是这个套路)。现实里到处是弱实体:订单项(第几项 + 属于哪个订单)、章节(第几章 + 属于哪本书)。识别出弱实体,主码自然就对了,不会犯「给副本发全局唯一 ID」这种过度设计。