數據庫設計三范式
作為一個數據庫的學習者,搞懂關系數據庫的三大范式是很有用的。然而有關數據庫范式的介紹都是采用學術性的定義,語法羞澀,讓人難懂,故寫下自己對數據庫范式的理解,給初學者提供幫助。
關系數據庫中的關系必須滿足一定的要求。滿足不同程度要求的為不同范式。數據庫的設計范式是數據庫設計所需要滿足的規范。只有理解數據庫的設計范式,才能設計出高效率、優雅的數據庫,否則可能會設計出錯誤的數據庫。
目前,主要有六種范式:第一范式、第二范式、第三范式、BC范式、第四范式和第五范式。滿足最低要求的叫第一范式,簡稱1NF。在第一范式基礎上進一步滿足一些要求的為第二范式,簡稱2NF。其余依此類推。
范式可以避免數據冗余,減少數據庫的空間,減輕維護數據完整性的麻煩,但是操作困難,因為需要聯系多個表才能得到所需要數據,而且范式越高性能就會越差。要權衡是否使用更高范式是比較麻煩的,一般在項目中,用得最多的也就是第三范式,使用到第三范式也就足夠了,性能好而且方便管理數據。
本文不介紹規范化程度高于3NF的范式,對于很多大型復雜的系統,其數據庫設計都沒有遵循所謂的范式,這也是為什么會出現所謂的逆規范化,范式也需要考慮使用場景,不可一切東西都要范式化。在沒有更多實踐經驗的情況下,遵循范式是非常好的選擇。
在實例中理解三大范式
1NF:字段不可分
強調的是列的原子性,即列不能夠再分成其他幾列。
例1,學生信息表
- 學生編號 姓名 性別 聯系方式
- 20080901 張三 男 email:zs@126.com,phone:88886666
- 20080902 李四 女 email:ls@126.com,phone:66668888
- 以上的表就不符合,第一范式:聯系方式字段可以再分,所以變更為正確的是:
- 學生編號 姓名 性別 電子郵件 電話
- 20080901 張三 男 zs@126.com 88886666
- 20080902 李四 女 ls@126.com 66668888
例2,學生班級信息
- 學生編號 姓名 班級
- 20080901 小明 高三1班
- 20080902 小葉 高三2班
- 以上的表就不符合,第一范式:班級字段可以再分,所以變更為正確的是:
- 學生編號 姓名 年級 班級
- 20080901 小明 高三 1班
- 20080902 小葉 高三 2班
例3,員工信息表
- 員工編號 姓名 工作年限
- 20080901 小明 2009~2011
- 20080902 小葉 2006~2012
- 以上的表就不符合,第一范式:工作年限可以再分,所以變更為正確的是:
- 員工編號 姓名 工作年份 離職年份
- 20080901 小明 2009 2011
- 20080902 小葉 2006 2012
例4,學生成績表
- 學生編號 姓名 課程成績
- 20080901 小明 80,70,90
- 20080902 小葉 60,70,85
- 以上的表就不符合,第一范式:課程成績可以再分,所以變更為正確的是:
- 學生編號 姓名 語文 數學 外語
- 20080901 小明 80 70 90
- 20080902 小葉 60 70 85
例5,聯系人信息表
- 姓名 性別 電話
- 小明 男 0101-3464554,13699170707
- 小葉 女 0101-3464674,13623450707
- 以上的表就不符合,第一范式:電話可以再分,所以變更為正確的是:
- 姓名 性別 座機 手機
- 小明 男 0101-3464554 13699170707
- 小葉 女 0101-3464674 13623450707
例6,公司信息表
- 公司編號 名稱 地址
- 20080901 谷歌 美國加利福尼亞州圣克拉拉縣山景市
- 20080902 百度 中國北京市海淀區上地十街10號百度大廈
- 以上的表就不符合,第一范式:地址可以再分,所以變更為正確的是:
- 公司編號 名稱 國籍 地址
- 20080901 谷歌 美國 加利福尼亞州圣克拉拉縣山景市
- 20080902 百度 中國 北京市海淀區上地十街10號百度大廈
對于例6地址的拆分可根據需求進行,不一定非要拆分。如果需知道哪個國家并按其分類,那么顯然第一個表格是不容易滿足需求的,也不符合第一范式。因此是否符合第一范式的要求在一定程度上取決于后期對數據的查詢和使用上,當然,第一范式是前人總結的通用方法,遵循它會得到意想不到的好處。
2NF:有主鍵,非主鍵字段依賴主鍵
首先是滿足 1NF,另外包含兩部分內容,一是表必須有一個主鍵;二是沒有包含在主鍵中的列必須完全依賴于主鍵。
主鍵很重要,要記住在設計表的時候無論如何也要添加主鍵,沒有主鍵的表會給你帶來噩夢般的體驗,會給系統開發、功能維護、數據維護帶來不必要的麻煩。舉個例子,上面例5,聯系人信息表就沒有添加主鍵,如下數據你想刪除小明的信息該如何操作,根據姓名嗎?不行,因為有重名的情況,只能通過姓名+電話兩個字段組合為一個唯一的條件進行刪除。
姓名 性別 電話
- 小明 男 0101-3464554,13699170707
- 小葉 女 0101-3464674,13623450707
- 小朱 女 0101-3464675,13623450705
- 小明 男 0101-3464676,13623450706
而有了主鍵,情況會是怎樣的呢?你只需要根據聯系人編號即可刪除,一步到位。
聯系人編號 姓名 性別 電話
- 1 小明 男 0101-3464554,13699170707
- 2 小葉 女 0101-3464674,13623450707
- 3 小朱 女 0101-3464675,13623450705
- 4 小明 男 0101-3464676,13623450706
切記,在任何時刻,一張表一定要有主鍵,如果你無法確定業務中哪個字段作為主鍵,那么你就建立一個ID字段作為主鍵,多一個ID字段不會影響什么。
例1,學生信息表(主鍵學號)
- 學號 姓名 性別 年齡 課程名稱 學分
- 2008 張三 男 15 語文 45
- 2008 張三 男 15 數學 55
- 2009 李四 女 16 語文 45
- 2009 李四 女 16 數學 55
以上的表就不符合,第二范式:主鍵(學號)無法唯一確定課程名稱和學分,也就是說部分非主鍵字段不依賴主鍵,所以變更為正確的是:
學生信息表
- 學號 姓名 性別 年齡
- 2008 張三 男 15
- 2008 張三 男 15
課程表
- 課程名稱 學分
- 語文 45
- 數學 55
- 學生選課表
- 學號 課程名稱
- 2008 語文
- 2008 數學
- 2009 語文
- 2009 數學
例2,學生借書表
- 學生證號 學生證辦理時間 借書證號 借書證辦理時間
- 2008 2010年9月1號 201001 2010年10月1號
- 2009 2010年9月2號 201011 2011年10月1號
以上的表就不符合,第二范式:借書證號和借書證辦理時間這些非主鍵字段不依賴學生證號這個主鍵,所以變更為正確的是:
學生證表
- 學生證號 學生證辦理時間
- 2008 2010年9月1號
- 2009 2010年9月2號
借書證表
- 借書證號 借書證辦理時間
- 201001 2010年10月1號
- 201011 2011年10月1號
例3,訂單表
2NF在1NF的基礎之上更進一層。2NF需要確保數據庫表中的每一列都和主鍵相關,而不能只與主鍵的某一部分相關(主要針對聯合主鍵而言)。也就是說在一個數據庫表中,一個表中只能保存一種數據,不可以把多種數據保存在同一張數據庫表中。
比如要設計一個訂單信息表,因為訂單中可能會有多種商品,所以要將訂單編號和商品編號作為數據庫表的聯合主鍵,如下表所示:
訂單信息表(訂單編號和商品編號為聯合主鍵)
- 訂單編號 商品編號 商品名稱 數量 單位 價格 客戶 所屬單位 聯系方式
- 001 1 挖掘機 1 臺 1200000¥ 張三 上海玖智 020-1234567
- 001 2 沖擊鉆 8 把 230¥ 張三 上海玖智 020-1234567
- 002 3 鏟車 2 輛 980000¥ 李四 北京公司 010-1234567
這樣就產生一個問題:這個表中是以訂單編號和商品編號作為聯合主鍵。這樣在該表中商品名稱、單位、商品價格等信息不與該表的主鍵相關,而僅僅是與商品編號相關。所以在這里違反了2NF的設計原則。而如果把這個訂單信息表進行拆分,把商品信息分離到另一個表中,把訂單項目表也分離到另一個表中,就非常完美了。如下所示:
訂單信息表
- 訂單編號 客戶 所屬單位 聯系方式
- 001 張三 上海玖智 020-1234567
- 002 李四 北京公司 010-1234567
訂單項目表
- 訂單編號 商品編號 數量
- 001 1 1
- 001 2 8
- 002 3 2
商品信息表
- 商品編號 商品名稱 單位 商品價格
- 1 挖掘機 臺 1200000¥
- 2 沖擊鉆 個 230¥
- 3 鏟車 輛 980000¥
這樣設計,在很大程度上減小了數據庫的冗余。如果要獲取訂單的商品信息,使用商品編號到商品信息表中查詢即可。
3NF:非主鍵字段不能相互依賴
首先是 2NF,另外非主鍵列必須直接依賴于主鍵,不能存在傳遞依賴。即不能存在:非主鍵列 A 依賴于非主鍵列 B,非主鍵列 B 依賴于主鍵的情況。 通俗解釋:任意一個字段都只依賴表中的同一個字段。
例1,家庭成員表
- 戶主 兒子 女兒 女兒的小熊 女兒的海綿寶寶
- Jack Tom Lucy Bear spongebob
- Jobs july Lily Bear2 spongebob2
以上的表就不符合,第三范式:其中 兒子 ,女兒 等非主鍵列都完全依賴于主鍵(戶主),所以符合 2NF,不過問題是 女兒的小熊 ,女兒的海綿寶寶 直接依賴的是 女兒字段 (非主鍵列),而不是直接依賴于主鍵,它通過傳遞才依賴于主鍵,所以不符合 3NF。 所以變更為正確的是:
戶主信息表
- 戶主 兒子 女兒
- Jack Tom Lucy
- Josb July Lily
女兒信息表
- 女兒 女兒的小熊 女兒的海綿寶寶
- Lucy Bear spongebob
- Lily Bear2 spongebob2
例2,訂單表(主鍵是OrderID)
- OrderID OrderDate CustomerID CustomerName CustomerAddr CustomerCity
- 101 2011年 100 xx聯合公司 中央大街100號 紐約
- 102 2012年 100 xx聯合公司 中央大街100號 紐約
- 103 2014年 200 yy聯合公司 白宮 紐約
以上的表就不符合,第三范式:其中 OrderDate,CustomerID等非主鍵列都完全依賴于主鍵(OrderID),所以符合 2NF,不過問題是 CustomerName,CustomerAddr,CustomerCity 直接依賴的是 CustomerID(非主鍵列),而不是直接依賴于主鍵,它是通過傳遞才依賴于主鍵,所以不符合 3NF。 所以變更為正確的是:
訂單表
- OrderID OrderDate CustomerID
- 101 2011年 100
- 102 2012年 100
- 103 2014年 200
客戶信息表
- CustomerID CustomerName CustomerAddr CustomerCity
- 100 xx聯合公司 中央大街100號 紐約
- 200 yy聯合公司 白宮 紐約
例3,學生信息表(主鍵是學號)
- 學號 姓名 所在系 系名稱 系地址
- 101 小明 001 數學系 1號樓
- 102 小葉 002 文學系 5號樓
- 103 小炫 003 物理系 6號樓
以上的表就不符合,第三范式:其中學號,姓名,所在系等非主鍵列都完全依賴于主鍵(學號),所以符合 2NF,不過問題是系名稱,系地址直接依賴的是所在系(非主鍵列),而不是直接依賴于主鍵,它是通過傳遞才依賴于主鍵,所以不符合 3NF。 所以變更為正確的是:
學生表
- 學號 姓名 所在系
- 101 小明 001
- 102 小葉 002
- 103 小炫 003
院系信息表
- 系編號 系名稱 系地址
- 001 數學系 1號樓
- 002 文學系 5號樓
- 003 物理系 6號樓
第二范式(2NF)和第三范式(3NF)的概念很容易混淆,區分它們的關鍵點在于,2NF:非主鍵列是否完全依賴于主鍵,還是依賴于主鍵的一部分;3NF:非主鍵列是直接依賴于主鍵,還是直接依賴于非主鍵列。
通過對每個范式的定義介紹,加上幾個在實踐中的反例進行講解,可在一定程度上加快理論的理解,縮短理論和實踐之間的距離,可快速上手3范式在開發中的應用。
【本文為51CTO專欄作者“朱國立”的原創稿件,轉載請通過作者微信公眾號“開發者圓桌”獲取聯系和授權】