有了副管理员,1:N 就变成 N:M
到上一篇为止,手机请柬服务里的关系都是 1:N。一位管理员创建多份请柬,一份请柬只对应创建它的那一位管理员。所以请柬表上有一个 admin_id 列就够了。这个格子里放的是指向管理员表中那位管理员所在行的 id。
假设服务做起来了,招了好几位管理员,请柬也多了起来。如果只允许创建请柬的管理员修改,那么等联系上这位管理员再改好,要花太长时间。于是需要能更快响应的副管理员。
现在一份请柬由多位管理员负责,一位管理员也照样负责多份请柬。两边都是「多」的这种关系叫多对多,写作 N:M。在 1:N 里「多」只挂在一边,在 N:M 里两边都挂着。
到这里,本系列看过的关系一共三种:作为基础的 1:N、偶尔使用的可选 1:1,以及这次的 N:M。
不往一个格子里放列表 — 用中间表来解
最先想到的办法,是在 admin_id 格子里以列表形式放多位管理员,比如在请柬 1 的格子里一次写下管理员 1、2、3。意思上说得通,但表不是这么用的。关系型数据库的设计以「一个格子放一个值」为基础。
取而代之,把关系一行一条地记下来:请柬 1 与管理员 1、请柬 1 与管理员 2、请柬 1 与管理员 3、请柬 2 与管理员 4。为此另建一张只记关系的表。在课程的例子里它叫「请柬管理」,格子是自己的 id、请柬 id 和管理员 id。
这样一个 N:M 就变成了两个 1:N。一份请柬下挂着多行请柬管理,一位管理员下也挂着多行请柬管理。只是把上一篇的 1:N 用了两次,没有新规则要学。
这张中间表有专门的叫法。课程里称它为连接对象(junction object),意思是为了说明两张表之间的关系而建的对象。连接表、关联表这些叫法指的也是同一个东西。
invitations admins invitation_admins
─────────── ────────── ──────────────────────────────
id groom_name id name id invitation_id admin_id
1 敏秀 1 智恩 1 1 1
2 俊浩 2 书俊 2 1 2
3 夏琳 3 1 3
4 道允 4 2 4
invitations 1 : N invitation_admins
admins 1 : N invitation_admins中间表里可以记下角色
把关系单独拿出来还有额外的好处:可以给中间表加更多的列。这等于有了一个记录关系本身信息的位置。
课程的例子是 role(角色)列。对请柬 1 来说,管理员 1 是最初创建的人,所以是 owner;其余的人不是主人,但能处理事务,所以记为 sub。同一位管理员在不同请柬上的角色可能不同,所以这个值既不该放在管理员表,也不该放在请柬表,而应放在两者之间的那一行里。
所以遇到 N:M,主要的解法就是建一张中间表,把两边分别用 1:N 连上。原本只有管理员和请柬的简单 ERD,每加一个功能就会这样扩展开来。
向 AI 提这个结构时,用文字把形状定下来就行。像下面的请求那样先写明中间表的名字和格子,就不太会得到把管理员列表塞进一个格子的答案。把已有请柬的 admin_id 值搬到新表,属于迁移(migration),也就是把现有数据挪到新结构的变更,需要单独确认。这部分在第 4 篇讲。
| invitation_id | admin_id | role |
|---|---|---|
| 1 | 1 | owner |
| 1 | 2 | sub |
| 1 | 3 | sub |
| 2 | 4 | owner |
我想让一份请柬可以由多位管理员管理。
不要在请柬表的 admin_id 格子里放管理员列表,
请建中间表 invitation_admins(id, invitation_id, admin_id, role),
把 invitations 和 admins 分别用 1:N 连上。
role 里只允许 owner 或 sub。
动手之前,先说明现有的请柬数据要怎么迁移。连接 — 用 id 拉来自己表里没有的信息
这次假设要做请柬列表页面。除了新郎、新娘这些请柬信息,还想一眼看到这份请柬用的是哪个模板,以及该模板的 URL 和缩略图(小尺寸的预览图)。
可是请柬表里没有模板 URL。上一篇把模板拆成了单独的表,所以请柬那一行只有 template_id,URL 和缩略图在模板表的行里。
于是拿着请柬行里的 template_id,到模板表里找 id 相同的那一行,把那一行的 URL 取过来,以 template_url 的名字贴在请柬行旁边。自己的表里没有、但父表里有的信息,用 id 拉过来贴上,这就是连接(join)。
把列表页交给 AI 时,加一句「模板 URL 和缩略图从模板表连接过来」就行。比起把同样的值再复制一份到请柬表,用一个 id 拉取的方式在修改模板时不会出现数值对不上。
invitations templates
─────────────────────────────────────── ────────────────────────────────
id groom_name bride_name template_id id url thumbnail
1 敏秀 书妍 t-03 t-03 /t/spring spring.png
列表页上显示的一行(连接结果)
────────────────────────────────────────────────────────────
id groom_name bride_name template_url template_thumbnail
1 敏秀 书妍 /t/spring spring.png用 Mermaid 请 AI 画出 ERD
关系多到这个程度,光靠文字就很难跟上了。这时用的是 Mermaid。这个名字在英语里是美人鱼的意思,它是把用文字写成的代码变成图的工具。
和 AI 一起工作,会大量使用 Markdown 文档。Markdown 是用 #、- 这类符号写标题和列表的文档格式,像 README.md 这样名字以 .md 结尾的文件就是。在这种文档里用三个反引号(`)开一个代码块,写上 mermaid,再在下一行写 erDiagram 以及表和关系,支持 Mermaid 的界面就会自动画出 ERD。
不需要自己写代码。课程推荐的做法是对 AI 说「用 Mermaid 画出 ERD」。下面就是把本篇出现的四张表这样写出来的代码。||--o{ 这样的符号就是第 2 篇讲的鸦爪表示法;PK 是指向该行的唯一 id(主键),FK 是指向别的表 id 的格子(外键)。
课程说的要领是:看着画出来的图,亲手把关系一条条过一遍。在图上确认请柬和管理员之间有没有中间表,列表页需要的信息是从哪张表连接过来的。下一篇看看把这样画好的结构交给 AI 时会出事的四种情况,以及如何从「列表很慢」「注册成功了积分却没到账」这样的症状认出它们。
把这个项目现在的数据库结构画成 Mermaid erDiagram。
每张表都写上列名、数据类型和 PK·FK 标记,
关系用鸦爪表示法画出来,让人看出是 1:N 还是 N:M。
如果有 N:M,也说明是用哪张中间表解开的。```mermaid
erDiagram
admins ||--o{ invitation_admins : manages
invitations ||--|{ invitation_admins : "managed by"
templates ||--o{ invitations : "used by"
admins {
uuid id PK
varchar name
}
invitations {
uuid id PK
varchar groom_name
varchar bride_name
uuid template_id FK
}
invitation_admins {
uuid id PK
uuid invitation_id FK
uuid admin_id FK
varchar role "owner or sub"
}
templates {
uuid id PK
varchar url
varchar thumbnail
}
```