前面我們介紹了第二正規化、第三正規化,還有第一正規化的後半。
現在終於要進入第一正規化的前半,也就是整個正規化形式的最開頭。
第一正規化的前半告訴我們:表單要有一個欄位是主鍵(Primary Key)。
什麼是主鍵?為什麼需要它?我們來看一個例子。
為什麼需要主鍵
假設你有一個會員表單,用「會員姓名」來識別每一筆資料:
| 會員姓名 | 會員信箱 |
|---|---|
| David | david@mail.com |
| Emily | emily@mail.com |
| Frank | frank@mail.com |
看起來沒問題,每個人的姓名都不一樣,可以分辨誰是誰。
但現在要新增一筆會員資料,這個人也叫 David:
| 會員姓名 | 會員信箱 |
|---|---|
| David | david@mail.com |
| Emily | emily@mail.com |
| Frank | frank@mail.com |
| David | another.david@mail.com |
問題來了:怎麼分辨這是兩個不同的 David?
如果你想修改第一個 David 的信箱,你要怎麼告訴資料庫「我要改的是第一個 David,不是第二個」?
這就是「識別資料」的問題。
用姓名識別的問題
除了「姓名可能會重複」之外,還有兩個問題。
姓名可能會改變。如果第一個 David 改名叫 Kevin 呢?
| 會員姓名 | 會員信箱 |
|---|---|
| Kevin | david@mail.com |
| David | another.david@mail.com |
如果其他表單(例如會員電話表)是用「David」來關聯這個會員,現在就找不到了,因為 David 已經變成 Kevin 了。
姓名可能是空的。如果有人還沒填姓名呢?
| 會員姓名 | 會員信箱 |
|---|---|
| Kevin | david@mail.com |
| David | another.david@mail.com |
| test@mail.com |
第三筆資料是誰?不知道,根本無法識別。
結論:用姓名來識別會員,會遇到重複、改變、空值的問題。
主鍵(Primary Key)
從上面的例子可以發現,用「姓名」來識別會員會遇到三個問題:
- 可能會重複(兩個 David)
- 可能會改變(David 改名叫 Kevin)
- 可能是空的(有人還沒填姓名)
所以,我們需要一個欄位,專門用來識別「這是哪一筆資料」。這個欄位就叫做主鍵(Primary Key)。
主鍵的作用是:唯一識別每一筆資料。有了主鍵,不管你要查詢、修改、刪除哪一筆資料,都能準確找到它。
主鍵的三個條件
主鍵必須符合三個條件:
- 不能重複:如果重複了,就無法分辨是哪一筆
- 不應該改變:如果改了,其他表單原本用這個值來關聯,就會找不到資料。像前面的例子,會員電話表用「David」關聯會員,結果 David 改名成 Kevin,關聯就斷了
- 不能是空值:如果是空的,就不知道這是哪一筆資料
在資料庫中,你可以對某個欄位加上 PRIMARY KEY,告訴資料庫「這個欄位是主鍵」。加上之後,資料庫會自動幫你:
- 加上 NOT NULL 限制(不能是空值)
- 加上 UNIQUE 限制(不能重複)
另外,每張表單最多只能有一個 PRIMARY KEY。為什麼?如果有兩個主鍵,當它們的值互相衝突時,要以哪個為準?不知道。所以每張表單只能有一個主鍵,用它來唯一識別每一筆資料。
主鍵應該用什麼值?
現在問題來了:什麼欄位適合當主鍵?
姓名不適合當主鍵
姓名可能重複(兩個人都叫 David),也可能改變(改名)。
Email 不適合當主鍵
Email 可能改變(換信箱)。
用 ID 當主鍵
姓名會重複、會改變。Email 會改變。現實世界的資料,好像都不太適合當主鍵。
那怎麼辦?
既然現實世界的資料都不可靠,那就自己創造一個專門用來識別的編號。這個編號:
- 沒有任何實際意義,純粹用來識別
- 由系統自動產生,保證不重複
- 永遠不會改變
這個編號通常叫做 ID(Identifier,識別碼)。這就是為什麼你會看到資料庫的表單裡,幾乎都有一個「ID」欄位,它就是拿來當主鍵用的。
流水號 vs UUID
「毫無意義的編號」有兩種常見的做法:
流水號
按順序編號,簡單易懂。
| 會員 ID | 會員姓名 |
|---|---|
| 1 | David |
| 2 | Emily |
| 3 | Frank |
優點:簡單、好讀
缺點:可能洩漏商業資訊
例如,如果訂單編號是流水號:
- 月初買一次,訂單編號是 1001
- 月底買一次,訂單編號是 1532
別人就能推算出你這個月有大約 500 張訂單,進而估算營業額。
UUID
一串看起來很亂的英文和數字,例如:f47ac10b-58cc-4372-a567-0e02b2c3d479
優點:無法從編號推測任何資訊
缺點:不好讀、佔用較多空間
流水號和 UUID 怎麼選
- 對內使用(例如內部管理系統):流水號就可以了,簡單方便
- 對外曝露(例如給客戶看的訂單編號):建議用 UUID,避免洩漏資訊
現在大多數資料庫都支援自動產生 UUID,不用自己想怎麼生成。
主鍵讓關聯更穩定
還記得之前的會員電話表嗎?
以前的做法(用姓名關聯):
會員表單:
| 會員姓名 | 會員信箱 |
|---|---|
| David | david@mail.com |
| Emily | emily@mail.com |
會員電話表單:
| 電話 | 所屬會員 |
|---|---|
| 0912-345-678 | David |
| 0922-111-222 | David |
如果 David 改名叫 Kevin,會發生什麼事?
會員表單:
| 會員姓名 | 會員信箱 |
|---|---|
| Kevin | david@mail.com |
| Emily | emily@mail.com |
會員電話表單:
| 電話 | 所屬會員 |
|---|---|
| 0912-345-678 | David |
| 0922-111-222 | David |
電話表的「所屬會員」還是寫 David,但會員表裡已經沒有 David 了,變成 Kevin。關聯就斷掉了!
現在的做法(用主鍵關聯):
會員表單:
| 會員 ID(PK) | 會員姓名 |
|---|---|
| U001 | David |
| U002 | Emily |
會員電話表單:
| 電話 | 會員 ID |
|---|---|
| 0912-345-678 | U001 |
| 0922-111-222 | U001 |
現在電話表關聯的是「會員 ID」,不是姓名。
如果 David 改名叫 Kevin:
| 會員 ID(PK) | 會員姓名 |
|---|---|
| U001 | Kevin |
| U002 | Emily |
電話表完全不用改!因為它關聯的是 U001,而 U001 還是那個人。
這就是主鍵的威力:其他欄位再怎麼改,都不會影響資料之間的關聯。
PRIMARY KEY 重點整理
這篇文章介紹了第一正規化的前半:
- 問題:怎麼識別「這是哪一筆資料」?欄位的值可能重複或改變
- 解法:加上主鍵(Primary Key)欄位
- 主鍵的條件:不能是空值、不能重複、不應該改變
- PRIMARY KEY 限制:自動包含 NOT NULL 和 UNIQUE,每張表最多一個
- 主鍵的值:建議用毫無意義的編號(流水號或 UUID)
- 好處:其他欄位再怎麼改,都不會影響資料之間的關聯
到目前為止,我們已經學了三種欄位限制:
- UNIQUE:確保欄位的值不重複
- NOT NULL:確保欄位一定要有值
- PRIMARY KEY:確保可以唯一識別每一筆資料(包含 UNIQUE + NOT NULL)
前三個正規化形式也都介紹完了!這些原則的核心都是一樣的:讓資料庫的結構乾淨、不重複、好維護。