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