上一篇文章介紹了第一正規化形式,我們知道「一個會員有多支電話」這種情況要拆表單。
但這種「一個對應到多個」的關係,在資料庫設計中有專門的名稱,叫做「一對多關係」。
除了一對多,還有「一對一」和「多對多」。這篇文章會教你怎麼判斷資料之間是哪種關係,以及不同的關係要怎麼設計表單。
三種關係類型
在資料庫設計中,當有人問你「會員和電話是什麼關係?」,你只需要回答三個選項之一:
- 一對一
- 一對多(或多對一)
- 多對多
判斷的方法很簡單,問自己兩個問題:
- 一個會員最多可以有幾支電話?
- 一支電話最多可以被幾個會員持有?
注意關鍵字是「最多」,不是「最少」。
以會員和電話為例:
- 一個會員最多可以有幾支電話?→ 很多支
- 一支電話最多可以被幾個會員持有?→ 一個
所以會員和電話是「一對多」的關係。
生活中的關係類型練習
我們來看一些實際的例子,練習判斷關係類型。
會員和身分證字號
你可能會想:一個會員只有一個身分證字號,一個身分證字號只屬於一個會員,應該是「一對一」吧?
很可惜,不是。
根據戶政資料,全台灣大概有一兩千人的身分證字號是重複的。這是因為早期報戶口時的疏失,導致有些人的身分證字號跟別人一樣。
所以:
- 一個會員最多有幾個身分證字號?→ 一個
- 一個身分證字號最多被幾個會員持有?→ 多個
會員和身分證字號其實是「多對一」的關係。
這也是為什麼設計資料庫時,不要用身分證字號當作識別會員的唯一依據,因為它可能會重複。
會員和 VIP 編號
假設你的 VIP 編號設計得很嚴謹:
- 一個會員最多有幾個 VIP 編號?→ 一個
- 一個 VIP 編號最多屬於幾個會員?→ 一個
這就是「一對一」的關係。
注意:有些會員可能還沒升級成 VIP,所以沒有 VIP 編號。但我們問的是「最多」,不是「最少」。
會員和生日
- 一個會員最多有幾個生日?→ 一個(一個人只會在一天出生)
- 一個日期最多是幾個會員的生日?→ 很多個
會員和生日是「多對一」的關係。
會員和生父
- 一個會員最多有幾個生父?→ 一個
- 一個生父最多有幾個小孩(會員)?→ 很多個
會員和生父是「多對一」的關係。
會員和配偶
以現行中華民國法律來說,不允許重婚:
- 一個會員最多有幾個配偶?→ 一個
- 一個配偶最多是幾個會員的配偶?→ 一個
會員和配偶是「一對一」的關係。
(如果把歷史上所有的配偶都算進來,就會變成「多對多」了)
會員和好友
假設是類似 Facebook 的好友關係:
- 一個會員最多可以有幾個好友?→ 很多個
- 一個人最多可以被幾個會員加為好友?→ 很多個
會員和好友是「多對多」的關係。
更多例子:書本和商品
書本和作者
- 一本書最多有幾個作者?→ 很多個(合著)
- 一個作者最多寫幾本書?→ 很多本
書本和作者是「多對多」的關係。
商品和消費者
- 一個商品最多被幾個消費者購買?→ 很多個
- 一個消費者最多購買幾個商品?→ 很多個
商品和消費者是「多對多」的關係。
商品和訂單
- 一個商品最多出現在幾張訂單裡?→ 很多張
- 一張訂單最多包含幾個商品?→ 很多個
商品和訂單是「多對多」的關係。
不同關係類型要怎麼設計表單?
你會發現,現實生活中的關係,很多都是「一對多」或「多對多」。
不同的關係類型,需要不同的表單設計方式。
一對一關係
一對一關係最單純,你可以選擇要不要拆表單。
做法一:放在同一張表單
你可以直接把兩邊的資料放在同一張表單裡。
例如會員和 VIP 編號:
| 會員 ID | 會員名稱 | VIP 編號 |
|---|---|---|
| U001 | David | V001 |
| U002 | Emily | (無) |
一個會員最多一個 VIP 編號,一個 VIP 編號最多屬於一個會員,所以放在一起沒問題。
做法二:分成兩張表單
你也可以選擇分開,用 ID 關聯:
會員表單:
| 會員 ID | 會員名稱 |
|---|---|
| U001 | David |
| U002 | Emily |
VIP 表單:
| VIP 編號 | 會員 ID |
|---|---|
| V001 | U001 |
什麼時候會想分開?例如 VIP 資料很複雜,有很多欄位(等級、點數、到期日…),放在會員表單會讓表單太臃腫,這時候分開會比較清楚。
重點是:一對一關係你有選擇權,要合併或分開都可以。
一對多關係
一對多關係就不一樣了,必須拆成兩張表單。
為什麼?回想一下上一篇文章的例子:如果一個會員有三支電話,你不能在會員表單開三個欄位(電話一、電話二、電話三),這樣會違反第一正規化。
所以一對多關係一定要拆表單:
會員表單:
| 會員 ID | 會員名稱 |
|---|---|
| U001 | David |
會員電話表單:
| 電話 | 會員 ID |
|---|---|
| 0912-345-678 | U001 |
| 0922-111-222 | U001 |
| 0933-333-444 | U001 |
電話表單裡的「會員 ID」指向會員表單,記錄這支電話屬於哪個會員。
一對一和一對多的差異:
- 一對一:可以放同一張表單(不用拆)
- 一對多:必須拆成兩張表單(不拆會違反第一正規化)
多對多關係
多對多關係需要三張表單:兩張記錄個體,一張記錄關係。
例如書本和作者:
書本表單:
| 書本 ID | 書名 |
|---|---|
| B001 | 資料庫入門 |
| B002 | SQL 實戰 |
作者表單:
| 作者 ID | 作者名稱 |
|---|---|
| A001 | 王小明 |
| A002 | 李小華 |
書本作者關係表:
| 書本 ID | 作者 ID |
|---|---|
| B001 | A001 |
| B001 | A002 |
| B002 | A001 |
從關係表可以看出:
- 《資料庫入門》有兩個作者:王小明和李小華
- 《SQL 實戰》有一個作者:王小明
- 王小明寫了兩本書,李小華寫了一本書
這張「關係表」是多對多關係必須要有的,否則無法正確記錄關係。
多對多關係需要三張表單:書本表單、作者表單、關係表
特殊情況:自己對自己的多對多
有時候多對多關係是「自己對自己」,例如會員和好友。
一個會員可以有很多好友,一個人也可以被很多會員加為好友,所以是多對多關係。
但這裡有個特別的地方:好友也是會員。
如果是書本和作者,你需要「書本表單」和「作者表單」,因為書本和作者是不同的東西。
但會員和好友呢?好友本身就是另一個會員,不是什麼新的東西。所以你不需要另外開一張「好友表單」。
會員表單:
| 會員 ID | 會員名稱 |
|---|---|
| U001 | David |
| U002 | Emily |
| U003 | Frank |
好友關係表:
| 會員 ID | 好友 ID |
|---|---|
| U001 | U002 |
| U001 | U003 |
| U002 | U001 |
從關係表可以看出:
- David(U001)的好友有 Emily(U002)和 Frank(U003)
- Emily(U002)的好友有 David(U001)
注意「好友 ID」指向的也是會員表單,因為好友本身就是會員。
一般的多對多 vs 自己對自己的多對多:
| 一般的多對多 | 自己對自己的多對多 | |
|---|---|---|
| 例子 | 書本和作者 | 會員和好友 |
| 需要幾張表 | 3 張(書本 + 作者 + 關係表) | 2 張(會員 + 關係表) |
| 關係表指向 | 兩張不同的表 | 同一張表(會員表單) |
不管是哪種多對多,都需要一張關係表來記錄誰和誰有關係。
關係類型與表單數量
整理一下:
| 關係類型 | 最少需要幾張表單 | 說明 |
|---|---|---|
| 一對一 | 1~2 張 | 可以合併,也可以分開 |
| 一對多 | 2 張 | 「多」的那張表單記錄「一」的 ID |
| 多對多 | 3 張 | 兩張個體表 + 一張關係表 |
只要不是一對一,就一定要拆表單。
關係類型重點整理
這篇文章介紹了資料之間的三種關係類型:
- 判斷方法:問「一個 A 最多對應幾個 B」和「一個 B 最多對應幾個 A」
- 一對一:兩邊都是「一個」,可以放同一張表或分開
- 一對多:一邊是「一個」,另一邊是「很多個」,需要兩張表單
- 多對多:兩邊都是「很多個」,需要三張表單(含一張關係表)
- 現實中大多是一對多或多對多,所以拆表單是很常見的操作
- 不要用身分證字號當唯一識別,因為它可能會重複
學會判斷關係類型,你就能更有系統地設計資料庫的表單架構了。