上一篇文章我們學到了第二正規化形式:把相依的欄位放在同一張表單,不相依的欄位拆到不同的表單。
例如「會員」和「會員信箱」是相依的(知道會員就知道信箱),所以放在同一張會員表單。
但有時候,表單裡還會有另一種「多餘」的欄位——它不是直接重複,而是可以計算出來的。
這篇文章會介紹第三正規化形式,教你怎麼找出這些多餘的欄位,並且把它們刪掉。
第二正規化之後還有哪些多餘欄位
在第二正規化形式中,我們把「會員信箱」拆到會員表單,把「飯店電話」拆到飯店表單。
這些欄位都不是多餘的——你刪掉「會員信箱」,就真的找不到會員的信箱了;你刪掉「飯店電話」,就真的找不到飯店的電話了。
但是,有些欄位就不一樣了。
總價是多餘的嗎?
看一下我們目前的表單:
房型表單:
| 房型 ID | 房型名稱 | 單價 | 飯店 ID |
|---|---|---|---|
| R001 | 雙人房 | 3000 | H001 |
| R002 | 總統套房 | 10000 | H001 |
| R003 | 單人房 | 2000 | H002 |
訂房表單:
| 會員 ID | 房型 ID | 入住天數 | 總價 |
|---|---|---|---|
| U001 | R001 | 2 | 6000 |
| U001 | R003 | 3 | 6000 |
| U002 | R002 | 1 | 10000 |
你有沒有發現「總價」這個欄位怪怪的?
以第一筆資料為例:房型 ID 是 R001,從房型表單可以查到單價是 3000。然後 3000 × 2 天 = 6000,就是總價。
換句話說,總價是可以計算出來的,不需要另外存。
可推算欄位的問題
如果你把「總價」存在表單裡,會有什麼問題?
房型漲價要改總價
假設台北大飯店的雙人房要漲價,從 3000 改成 3500。
你需要做什麼?
- 去房型表單改「單價」→ 3500
- 去訂房表單改所有雙人房的「總價」→ 重新計算
如果這個房型已經被訂了 100 次,你就要重新計算 100 筆「總價」。
改天數也要改總價
假設會員想把入住天數從 3 天改成 2 天。
你需要做什麼?
- 改「入住天數」→ 2
- 改「總價」→ 重新計算
這樣每次修改,都要記得去改兩個地方。如果漏改了,資料就會不一致。
可推算的值也是一種重複
你可能會想:「總價」跟「單價」的值又不一樣,怎麼會是重複?
我們換個方式想:
- 如果你知道「單價」和「入住天數」,你能不能知道「總價」?→ 可以,算一下就知道了
- 如果你不知道「單價」和「入住天數」,光看「總價」有意義嗎?→ 有,但這個資訊其實是從前兩者來的
所以「總價」並沒有提供新的資訊,它只是把「單價 × 入住天數」的結果存起來而已。
把這種「可以算出來的值」另外存一份,就是一種「重複」——不是值的重複,而是資訊的重複。
第三正規化形式
第三正規化形式告訴我們:
如果一個欄位的值可以計算出來,就把它刪掉。
為什麼?因為這個欄位:
- 沒有提供新的資訊(它的值可以算出來)
- 會造成維護的負擔(別的欄位改了,它也要跟著改)
- 容易造成資料不一致(忘記改的話,資料就錯了)
所以,與其存一個會造成困擾的欄位,不如需要的時候再算就好。
刪除總價欄位
套用第三正規化形式,我們的訂房表單應該變成這樣:
| 會員 ID | 房型 ID | 入住天數 |
|---|---|---|
| U001 | R001 | 2 |
| U001 | R003 | 3 |
| U002 | R002 | 1 |
當你需要「總價」的時候,再去算:
- 從房型表單查到單價(例如 R001 的單價是 3000)
- 單價 × 入住天數 = 總價(3000 × 2 = 6000)
這樣就不會有資料不一致的問題了。
什麼時候可以保留可推算的欄位?
第三正規化形式並不是叫你「看到可推算的欄位就刪掉」。
有些情況下,你可能會刻意保留這些欄位。
計算邏輯不固定時可保留
如果「總價」不是單純的「單價 × 天數」,而是:
- 可能有折扣
- 可能有優惠活動
- 可能有會員專屬價格
- 可能有加購項目
這時候「總價」就不能單純從別的欄位算出來,因為計算邏輯會因人、因時而異。
在這種情況下,保留「總價」是合理的。
效能考量時可保留
如果計算總價需要查很多張表、做很複雜的運算,而且這個值經常被查詢,那你可能會選擇把它存起來,避免每次都重新計算。
但要記得:這是刻意違反正規化形式,你要能說出為什麼這樣做。
判斷標準
問自己一個問題:
這個欄位的值,是不是「永遠」可以從別的欄位算出來?
- 如果是 → 可以刪掉
- 如果不是(有例外情況)→ 保留
找出可推算欄位的練習
第三正規化形式其實就是在找「廢話」——那些你講了前兩句,就不用講第三句的情況。
來看幾個例子:
年齡可以從出生年算出來
「我出生於民國 100 年,今年是民國 124 年,我 24 歲。」
這三個資訊,你只需要其中兩個:
- 知道「出生年」和「今年」→ 可以算出「年齡」
- 知道「出生年」和「年齡」→ 可以算出「今年」
所以「年齡」是可推算的欄位,可以不用存。
是否破產可以從餘額算出來
「我的戶頭有 100 元,我要提領 120 元,我破產了。」
「是否破產」可以從「戶頭餘額」和「提領金額」推算出來:
- 提領金額 > 戶頭餘額 → 破產
所以不需要另外存一個「是否破產」的欄位。
星期幾可以從日期算出來
「我抵達美國的時間是 2015 年 6 月 30 日,星期二。」
「星期幾」可以從「日期」算出來,所以不需要另外存。
第二正規化 vs 第三正規化
來比較一下這兩個正規化形式:
| 第二正規化形式 | 第三正規化形式 | |
|---|---|---|
| 問題 | 同樣的資料重複出現很多次 | 可以算出來的值另外存了一份 |
| 原因 | 欄位之間有相依關係 | 欄位的值可以計算出來 |
| 解法 | 把相依的欄位拆到獨立的表單 | 把可推算的欄位刪掉 |
| 例子 | 會員 → 會員信箱 | 單價 × 天數 → 總價 |
兩個正規化形式的核心都是一樣的:避免重複資料。
只是第二正規化處理的是「直接的重複」,第三正規化處理的是「可推算的重複」。
第三正規化重點整理
這篇文章介紹了第三正規化形式的核心概念:
- 問題:有些欄位可以從別的欄位算出來,另外存一份就是重複
- 風險:別的欄位改了,這個欄位也要跟著改,容易造成資料不一致
- 第三正規化形式:如果欄位可以從別的欄位推算出來,就把它刪掉
- 例外情況:如果計算邏輯不固定,或有效能考量,可以保留
- 口訣:拆表單、刪欄位——第二正規化拆表單,第三正規化刪欄位
到目前為止,我們學的第二和第三正規化形式,都是從「欄位之間的相依關係」來決定要不要拆表單、刪欄位。
接下來的文章會介紹另一種角度:從「資料列」的層級來看,還有什麼需要調整的地方。