我們已經學了三種正規化形式:
- 第一正規化:表單要有主鍵,同一欄位只能存一筆資料
- 第二正規化:相依性高的欄位要拆表
- 第三正規化:可以從其他欄位推算出來的欄位要刪掉
也學了四種欄位限制:UNIQUE、NOT NULL、PRIMARY KEY、FOREIGN KEY。
現在問題來了:一對一、一對多、多對多的關係,什麼時候該拆表?
在學完正規化之後,你可能會有個直覺:什麼時候該拆表、什麼時候不用拆?
一對一的關係,通常不拆表。
例如:一個人只有一個身分證字號。這種情況下,把身分證字號放在人物表裡就好,不需要另外建一張「身分證表」。
一對多的關係,通常也不拆表。
例如:一個部門有很多員工,但一個員工只屬於一個部門。這種情況下,我們在員工表加一個「部門」欄位就好。不需要另外建一張「員工部門對照表」。
多對多的關係,通常要拆表。
例如:一個學生可以選很多門課,一門課也有很多學生選。這種情況下,我們沒辦法在學生表加一個「選的課」欄位(因為有很多門),也沒辦法在課程表加一個「選課的學生」欄位(因為有很多人)。所以我們需要另外建一張「選課表」,記錄「哪個學生選了哪門課」。
這個直覺大致正確,但實際上要看情境。讓我們用一個例子來說明。
一對一的關係就不拆表嗎
假設我們要記錄這個資訊:「David 是一個短髮的程式講師」。
你可能會這樣設計:
| 人物 ID(PK) | 姓名 | 髮型 | 職業 |
|---|---|---|---|
| 1 | David | 短髮 | 講師 |
看起來沒問題,一張表搞定。
講師有教授領域:一對一也能拆表
但如果我們在設計的是一個「線上教學系統」呢?
講師除了職業之外,還有一個很重要的資訊:教授領域。
| 人物 ID(PK) | 姓名 | 髮型 | 職業 | 教授領域 |
|---|---|---|---|---|
| 1 | David | 短髮 | 講師 | 程式 |
| 2 | Emily | 長髮 | 講師 | 哲學 |
| 3 | Frank | 短髮 | 學生 | ??? |
問題來了:Frank 是學生,不是講師,那「教授領域」要填什麼?
填 NULL?可以,但這樣每個學生的「教授領域」都是 NULL,有點浪費:
| 人物 ID(PK) | 姓名 | 髮型 | 職業 | 教授領域 |
|---|---|---|---|---|
| 1 | David | 短髮 | 講師 | 程式 |
| 2 | Emily | 長髮 | 講師 | 哲學 |
| 3 | Frank | 短髮 | 學生 | NULL |
| 4 | Grace | 長髮 | 學生 | NULL |
| 5 | Henry | 短髮 | 學生 | NULL |
| 6 | Ivy | 長髮 | 學生 | NULL |
如果學生有一百個,就會有一百個 NULL。這個欄位對學生來說根本沒有意義。
而且,「教授領域」跟「職業是講師」這件事有很強的相依性:
- 只有講師才有教授領域
- 如果是講師,就應該要有教授領域
這種情況下,我們可以考慮拆表:
人物表:
| 人物 ID(PK) | 姓名 | 髮型 | 職業 |
|---|---|---|---|
| 1 | David | 短髮 | 講師 |
| 2 | Emily | 長髮 | 講師 |
| 3 | Frank | 短髮 | 學生 |
講師表:
| 講師 ID(PK) | 人物 ID(FK) | 教授領域 |
|---|---|---|
| 1 | 1 | 程式 |
| 2 | 2 | 哲學 |
現在「教授領域」只會出現在講師表裡,不會有一堆 NULL。
這兩張表要怎麼看?
講師表裡的「人物 ID」是一個外部鍵,它指向人物表的「人物 ID」。
例如,講師表的第一筆資料:
- 講師 ID = 1
- 人物 ID = 1 → 對應到人物表的第一筆,也就是 David
- 教授領域 = 程式
所以這筆資料的意思是:「David 是一個講師,他教的領域是程式。」
同樣的,講師表的第二筆:
- 人物 ID = 2 → 對應到人物表的 Emily
- 教授領域 = 哲學
意思是:「Emily 是一個講師,她教的領域是哲學。」
而 Frank 是學生,他不在講師表裡,所以不需要填「教授領域」這個欄位。
結論:一對一的關係,如果「一」有額外的性質,而且這個性質跟某個條件有很強的相依性,就可以考慮拆表。
一對多的關係就不拆表嗎
接續上面的例子,假設現在有更多講師加入:
| 講師 ID(PK) | 人物 ID(FK) | 教授領域 |
|---|---|---|
| 1 | 1 | 程式 |
| 2 | 2 | 哲學 |
| 3 | 4 | 程式 |
| 4 | 5 | 程式 |
講師跟教授領域是什麼關係?
- 一個領域可以被很多講師教(程式被三個講師教)
- 一個講師目前只教一個領域
這是一個多對一的關係(多個講師對一個領域)。
按照之前學的,多對一的關係,我們把「一」記錄在「多」的表裡。所以「教授領域」放在講師表裡,看起來沒問題。
領域有熱度:違反第二正規化就該拆
但有一天,老闆說:「我想幫每個領域加一個『熱度』,表示這個領域受不受歡迎。」
例如:程式領域很熱門,哲學領域比較冷門。
如果我們直接加在講師表裡:
| 講師 ID(PK) | 人物 ID(FK) | 教授領域 | 領域熱度 |
|---|---|---|---|
| 1 | 1 | 程式 | 熱門 |
| 2 | 2 | 哲學 | 冷門 |
| 3 | 3 | 程式 | 熱門 |
問題來了:David 和 Frank 都教程式,「領域熱度」都是「熱門」。
這違反了第二正規化:「領域熱度」跟「教授領域」有很強的相依性,跟「講師」沒有關係。
所以我們應該拆表:
領域表:
| 領域 ID(PK) | 領域名稱 | 熱度 |
|---|---|---|
| 1 | 程式 | 熱門 |
| 2 | 哲學 | 冷門 |
講師表:
| 講師 ID(PK) | 人物 ID(FK) | 領域 ID(FK) |
|---|---|---|
| 1 | 1 | 1 |
| 2 | 2 | 2 |
| 3 | 3 | 1 |
現在「熱度」只記錄在領域表裡,不會重複。
結論:一對多的關係,如果「一」有自己的性質需要記錄,就應該拆表。
在這個例子裡,「領域」是「一」,「講師」是「多」。當領域有自己的性質(熱度)需要記錄時,我們就把領域拆成獨立的一張表。
這樣做的好處是:
- 領域的熱度只記錄一次,不會重複
- 如果要修改某個領域的熱度,只要改一個地方
- 符合第二正規化:相依性高的欄位放在一起
多對多的關係就一定要拆表嗎
接續上面的例子,老闆又說:「我想讓一個講師可以教多個領域。」
沒問題,講師表改成這樣:
| 講師 ID(PK) | 人物 ID(FK) | 領域 ID(FK) |
|---|---|---|
| 1 | 1 | 1 |
| 2 | 2 | 2 |
| 3 | 1 | 2 |
David(人物 ID = 1)現在可以教程式(領域 ID = 1)和哲學(領域 ID = 2)。
這是一個多對多的關係:
- 一個講師可以教多個領域
- 一個領域可以被多個講師教
按照正規化的原則,多對多的關係應該要拆表。目前這樣的設計是符合正規化的。
結構不穩定時可以用 JSON 保留彈性
但老闆又來了:「我想讓『熱度』不只跟領域有關,還要跟講師有關。」
什麼意思?
原本我們認為「熱度」是領域的性質:程式領域很熱門,哲學領域比較冷門。
但老闆說:「不對,熱度不只跟領域有關,還跟誰來教有關。」
例如,同樣是教程式:
- David 是知名講師,他教的課很多人買,熱度高
- Grace 是新手講師,他教的課比較少人買,熱度低
同一個領域,不同講師教,熱度不一樣。
這表示「熱度」不再只是領域的性質,而是「講師 + 領域」這個組合的性質。
如果我們繼續用拆表的方式,會變得很複雜:
- 領域表有領域的基本熱度
- 講師領域表有講師教這個領域的熱度
- 兩個熱度要怎麼整合?
而且老闆可能明天又改主意…
這時候,如果結構還不穩定,我們可以選擇暫時不拆表,用比較彈性的方式記錄:
| 講師 ID(PK) | 人物 ID(FK) | 領域(JSON) |
|---|---|---|
| 1 | 1 | [{“name”: “程式”, “熱度”: “高”}, {“name”: “哲學”, “熱度”: “中”}] |
| 2 | 2 | [{“name”: “哲學”, “熱度”: “低”}] |
這違反了第一正規化(同一欄位存多筆資料),但在結構不穩定的情況下,這樣做可以保留彈性。
我們來看一下這個表格:
| 講師 ID(PK) | 人物 ID(FK) | 領域(JSON) |
|---|---|---|
| 1 | 1 | [{“name”: “程式”, “熱度”: “高”}, {“name”: “哲學”, “熱度”: “中”}] |
| 2 | 2 | [{“name”: “哲學”, “熱度”: “低”}] |
第一筆資料是 David(人物 ID = 1):
- 他教兩個領域:程式和哲學
- 他教程式的熱度是「高」
- 他教哲學的熱度是「中」
第二筆資料是 Emily(人物 ID = 2):
- 她只教一個領域:哲學
- 她教哲學的熱度是「低」
你會發現,同樣是教哲學,David 的熱度是「中」,Emily 的熱度是「低」。這就是老闆要的:熱度跟講師有關,不只是跟領域有關。
這種做法的好處是:老闆改主意的時候,不用改資料庫結構,只要改 JSON 的內容就好。
等到老闆想清楚了,結構穩定了,再來拆表也不遲。
結論:多對多的關係,正規化原則下應該拆表。但如果結構還不穩定,可以暫時用彈性的方式記錄,等穩定後再拆。
重點整理
讓我們回顧一下這篇文章學到的內容:
一對一的關係
直覺做法:不拆表,放在同一張表就好。
什麼時候要拆表:當「一」有額外的性質,而且這個性質跟某個條件有強相依性。
例如:講師有「教授領域」這個性質,但只有講師才有教授領域,學生沒有。這時候把講師表拆出來,避免一堆 NULL。
一對多的關係
直覺做法:不拆表,在「多」的那張表加一個欄位就好。
什麼時候要拆表:當「一」有自己的性質需要記錄。
例如:多個講師教同一個領域。本來在講師表加一個「教授領域」欄位就好。但當領域有自己的性質(熱度)需要記錄時,就應該把領域表拆出來,避免違反第二正規化。
多對多的關係
直覺做法:拆表,建立一張關聯表。
什麼時候可以不拆:當結構還不穩定,老闆還在改主意的時候。
例如:講師和領域是多對多關係,而且熱度跟「講師 + 領域」的組合有關。如果結構還不穩定,可以先用 JSON 保留彈性,等穩定後再拆表。
最重要的觀念:不要一聽到一對一、一對多、多對多,就覺得一定要怎麼做。
要考慮的因素包括:
- 這個「一」有沒有額外的性質?
- 這個性質跟什麼條件有關?
- 現在的結構穩定嗎?還是還在探索階段?
正規化是原則,不是鐵律。理解原則背後的原因,才能在實際情境中做出正確的判斷。