關系型數據庫:實現正規化
2024-07-21 02:36:58
供稿:網友
設計階段,花在數據正規化上的時間可能比花上其他任何任務上的時間都要多。而且數據越多,這個過程所花的時間更長。根據以往的經驗,你可能發現最困難的就是滿足第一范式(1NF)的所有要求,因為將重復的值移動到另一個表時,經常會消除不恰當的依靠。
完成最困難的部分后,你可能選擇在1NF之后就停止了,但不要這樣做。請繼續對數據進行正規化,盡可能地通過第二范式(2NF),第三范式(3NF),甚至通過Boyce-Codd范式(BCNF)。這樣就能找出那些具有依靠性的數據元素,否則它們會在設計過程中靜靜地溜走,并在以后造成問題。最好在設計期間就發現這些問題——不要等到用戶發現自己的工作無法完成,或者等到你開始損失金錢的時候。
系列文章簡介
你現在正在閱讀的是Builder.com Relational Database Design系列中的一篇文章。假如錯過了前3篇文章,請首先閱讀它們:
《關系型數據庫:理論背后的靈感》
《關系型數據庫:使用范式創建數據庫》
《關系型數據庫:應用第一范式》
在該系列的上一篇文章中,我們已經從一個表著手,并對其進行了處理,使其符合1NF的要求。該表最終變成了4個表。現在,讓我們通過應用2NF、3NF和BCNF來完成正規化過程。
繼續未完的工作
我們的示范數據庫在完工以后,將用來存儲和書籍有關的數據;這是一個非常簡單的目的,所以只需要一個簡單的數據庫。我們現在已經有4個表,而且全部正規化為1NF(記住,要害字段是用星號表示的):
Books: {*Title, *ISBN, PRice}
Authors: {*FirstName, *LastName, *State, *Zip}
Categories: {*Category, Description}
Publishers: {*Publisher}
應用2NF
為了滿足2NF的要求,表首先必須正規化成1NF,也就是其中沒有多值項,沒有重復的組,每個字段都只能包含原子值,而且每個表都必須包含一個鍵。迄今為止,似乎所有表都滿足這個要求。2NF的第二個要求是所有字段(在設計階段通常稱為“屬性”)都必須依靠于主鍵,而且只能依靠于主鍵。就目前來看,似乎所有屬性都滿足2NF的要求,無需采取進一步的操作。
另一方面,假定Books表中還存儲著用于描述借閱者的大量屬性。有的屬性不會違反2NF的要求,例如Books表中的一個lent date(借閱日期)屬性。然而,其他數據(比如借閱者的姓名、地址等等)就會違反2NF,因為和借閱有關的信息不能完全地支持或描述書籍本身。
應用3NF
一個表在完成了2NF正規化后,可開始檢查它是否違反3NF。3NF要求所有字段都相互獨立。任何字段假如依靠于一個非要害字段,都必須轉移到另一個表中。為了找出違反3NF的地方,最簡單的方式就是修改每個屬性的值,看它是否立即使其他屬性所包含的數據無效。這種簡單測試雖然不能找出違反3NF的所有地方,但卻是一個不錯的開端。
Authors表存在一個可能違反3NF的地方:假如更改State值,那么可能同時還要更新ZIP;反之亦然。例如,假如作者移居另外一個州,那么上述兩個值都需要修改。為了避免這種形式的依靠性,你需要將State屬性轉移到一個新表中,如下所示:
Authors: {*FirstName, *LastName, *ZIP}
States: {*State}
上述修改的結果就是,每個作者都有了一個ZIP值,其中部分值可能重復,但在States表中,每個州只占用一條記錄。假如某個作者移居到其他某個州,你雖然需要更新ZIP值,但只需將記錄與一個不同的州聯系起來就可以了。假如是一個新出現的州,就可能需要輸入一個新的州值,但至少州值不會重復。
就目前來說,感覺是在創建一個查找表(lookup table)。以后,這些表會通過它們的主鍵和外鍵值相互聯系,但在正式建立聯系之前,按上述邏輯進行操作可能顯得比較困難。不過,假如搞不清楚當前的狀況,請不要擔心。目前只需將注重力集中在規則上就可以了。
現在已經有了5個表,全部正規化成3NF:
Books: {*Title, *ISBN, Price}
Authors: {*FirstName, *LastName, *ZIP}
Categories: {*Category, Description}
Publishers: {*Publisher}
States: {*State}
有人會對此產生疑問,因為有一個屬性似乎沒有考慮到,也就是 Authors表中的ZIP。前面說過,ZIP值是有可能重復的。在這個簡單的應用程序中,將ZIP留在Authors表中似乎是可以接受的;無論如何,數據庫都應該能高效地運行。不過,這個表并沒有充分地正規化,所以下面嘗試將ZIP轉移到一個新表中。在移動了ZIP之后,我們就有了6個表:
Books: {*Title, *ISBN, Price}
Authors: {*FirstName, *LastName}
ZIPCodes: {*ZIP}
Categories: {*Category, Description}
Publishers: {*Publisher}
States: {*State}
正規化不一定能保證效率
并非每個表都必須在完全正規化后才能獲得高效率。換言之,假如你發現能使數據庫變得更高效,那么完全可以對一個表進行反正規化處理。
應用BCNF
BCNF本質上是3NF的一個子規則,許多開發者認為它完全沒有必要,所以在3NF之后就停止正規化了。有人認為假如強制遵循這一規則,反而會降低性能。但對于目前通常都很強大的系統來說,性能恐怕不是一個大問題,除非你試圖操縱數百萬條記錄。當然,你不一定非要包括BCNF,必須權衡在進行了BCNF正規化之后,對性能造成的影響是否值得一個完全正規化的數據庫在靈活性上的好處。
BCNF要求任何屬性都絕對沒有機會依靠一個非要害字段。就目前來說,我們的表似乎能滿足這一要求。所以,讓我們在States表中添加一個City屬性,使局面變得復雜一些:
States: {*City, *State}
每個城市和州記錄都是惟一的,而且與一個ZIP值相關。但是,州值現在展示了一個重復的組,因為每個州都可能有多個城市。解決方案是將City屬性轉移到它自己的表中,如下所示:
Cities: {*City}
States: {*State}
雖然City和State屬性是一種不恰當的依靠,但實際是因為存在重復的值,所以才需要新建Cities表。這種問題通常在強制1NF時就能捕捉,但只有通過強制BCNF,才能最終完全捕捉到造成重復值的錯誤。通常,雖然一個依靠性問題會暫時迷惑你的眼睛,但只有在強迫了BCNF之后,才能徹底消除依靠非要害字段的問題。
對最初通過BCNF創建的表進行了正規化之后,表的總數就增加到了7個:
Books: {*Title, *ISBN, Price}
Authors: {*FirstName, *LastName}
ZIPCodes: {*ZIP}
Categories: {*Category, Description}
Publishers: {*Publisher}
States: {*State}
Cities: {*City}
表的數量雖然在快速增加,但請不要擔心。事實上,我們尚未完工。在本系列的下一篇文章中,甚至可能出現更多的表。屆時,我們將討論主鍵和外鍵字段,并解釋如何用它們在多個表之間建立關系。