範式應用
我們來逐步搞定一個論壇的數據庫,有如下信息:
(1) 用戶:用戶名,email,主頁,電話,聯繫地址
(2) 帖子:發帖標題,發帖內容,回覆標題,回覆內容
第一次我們將數據庫設計爲僅僅存在表:
用戶名 email 主頁 電話 聯繫地址 發帖標題 發帖內容 回覆標題 回覆內容
這個數據庫表符合第一範式,但是沒有任何一組候選關鍵字能決定數據庫表的整行,唯一的關鍵字段用戶名也不能完全決定整個元組。我們需要增加"發帖ID"、"回覆ID"字段,即將表修改爲:
用戶名 email 主頁 電話 聯繫地址 發帖ID 發帖標題 發帖內容 回覆ID 回覆標題 回覆內容
這樣數據表中的關鍵字(用戶名,發帖ID,回覆ID)能決定整行:
(用戶名,發帖ID,回覆ID) → (email,主頁,電話,聯繫地址,發帖標題,發帖內容,回覆標題,回覆內容)
但是,這樣的設計不符合第二範式,因爲存在如下決定關係:
(用戶名) → (email,主頁,電話,聯繫地址)
(發帖ID) → (發帖標題,發帖內容)
(回覆ID) → (回覆標題,回覆內容)
即非關鍵字段部分函數依賴於候選關鍵字段,很明顯,這個設計會導致大量的數據冗餘和操作異常。
我們將數據庫表分解爲(帶下劃線的爲關鍵字):
(1) 用戶信息:用戶名,email,主頁,電話,聯繫地址
(2) 帖子信息:發帖ID,標題,內容
(3) 回覆信息:回覆ID,標題,內容
(4) 發貼:用戶名,發帖ID
(5) 回覆:發帖ID,回覆ID
這樣的設計是滿足第1、2、3範式和BCNF範式要求的,但是這樣的設計是不是最好的呢?
不一定。
觀察可知,第4項"發帖"中的"用戶名"和"發帖ID"之間是1:N的關係,因此我們可以把"發帖"合併到第2項的"帖子信息"中;第5項"回覆"中的"發帖ID"和"回覆ID"之間也是1:N的關係,因此我們可以把"回覆"合併到第3項的"回覆信息"中。這樣可以一定量地減少數據冗餘,新的設計爲:
(1) 用戶信息:用戶名,email,主頁,電話,聯繫地址
(2) 帖子信息:用戶名,發帖ID,標題,內容
(3) 回覆信息:發帖ID,回覆ID,標題,內容
數據庫表1顯然滿足所有範式的要求;
數據庫表2中存在非關鍵字段"標題"、"內容"對關鍵字段"發帖ID"的部分函數依賴,即不滿足第二範式的要求,但是這一設計並不會導致數據冗餘和操作異常;
數據庫表3中也存在非關鍵字段"標題"、"內容"對關鍵字段"回覆ID"的部分函數依賴,也不滿足第二範式的要求,但是與數據庫表2相似,這一設計也不會導致數據冗餘和操作異常。
由此可以看出,並不一定要強行滿足範式的要求,對於1:N關係,當1的一邊合併到N的那邊後,N的那邊就不再滿足第二範式了,但是這種設計反而比較好!
對於M:N的關係,不能將M一邊或N一邊合併到另一邊去,這樣會導致不符合範式要求,同時導致操作異常和數據冗餘。
對於1:1的關係,我們可以將左邊的1或者右邊的1合併到另一邊去,設計導致不符合範式要求,但是並不會導致操作異常和數據冗餘。
結論
滿足範式要求的數據庫設計是結構清晰的,同時可避免數據冗餘和操作異常。這並意味着不符合範式要求的設計一定是錯誤的,在數據庫表中存在1:1或1:N關係這種較特殊的情況下,合併導致的不符合範式要求反而是合理的。
在我們設計數據庫的時候,一定要時刻考慮範式的要求。