關係型資料庫:實現正規化(轉)

post0發表於2007-08-14
關係型資料庫:實現正規化(轉)[@more@]

  設計階段,花在資料正規化上的時間可能比花上其他任何任務上的時間都要多。而且資料越多,這個過程所花的時間更長。根據以往的經驗,你可能發現最困難的就是滿足第一正規化(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}

  

  表的數量雖然在快速增加,但請不要擔心。事實上,我們尚未完工。在本系列的下一篇文章中,甚至可能出現更多的表。屆時,我們將討論主鍵和外來鍵欄位,並解釋如何用它們在多個表之間建立關係。

  


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/8225414/viewspace-957181/,如需轉載,請註明出處,否則將追究法律責任。

相關文章