SQL Server 進階 01 數據庫的設計
本篇目錄
1. 課程內容回顧及介紹
對於SQL Server基礎,我們已經學習了SQL Server的相關概念和基本操作,包括創建庫、創建表、添加約束和創建安全賬戶等。
掌握了對數據的增加(insert)、刪除(delete)、修改(update)、查詢(select)等SQL語句,主要知識點如下:
- 數據庫的產生背景和基礎知識
- 在SQL Server中創建庫、創建表
- 企業管理器和查詢分析器的概念
- 定義完整性約束以及強制完整性約束所需的約束
- 使用T-SQL操作表中的數據
- 用於查詢現有數據的T-SQL語句
- SQL Server中各種聚合函數
- 在SQL Server中用於查詢多個表的內鏈接
本系列教程,我們將深入學習SQL Server的高級應用,課程內容如下:
- 如何規范化的設計數據庫,如何繪制數據庫的E-R模型圖,方便項目團隊成員的高校溝通
- 如何編寫SQL語句,實現建庫、建表、加約束和創建安全賬戶,方便對數據庫的平穩移植
- 如何使用SQL Server的T-SQL語言,進行強大的SQL編程,實現多功能的數據管理
- 如何使用子查詢,實現復雜的單表或多表間的高級查詢
- 如何使用事務、視圖和索引,實現銀行轉賬等高效、安全的數據管理
- 如何創建存儲過程,在數據庫中實現高性能的數據管理
- 如何創建觸發器,根據業務規則,實現復雜的數據完整性約束
2.為什么需要規范的數據庫設計
您也許會問,在第一階段,根據業務需求,我們直接建庫、建表,插入測試數據,然后再查詢數據,為什么現在需要強調先設計再建庫、建表呢?
原因非常簡單,正如我們修造建築物一樣,如果您是蓋一間茅屋或一間簡易平房,您會花錢請人設計房屋圖紙嘛?毫無疑問,沒人請。
但是,如果是房地產開發商開發一個樓盤,修建多棟樓房的居住小區,他會情人設計施工圖紙嘛?答案是肯定的。
不但開發商會考慮設計施工圖紙,甚至恨專業的購房者也會在看房時要求開發商出示設計圖紙。
同樣道理,在實際的項目開發中,如果徐彤的數據存儲量較大,設計的表也比較多,表和表之間的關系比較復雜,我們就需要過濾規范的數據庫設計,然后再進行具體的建庫、建表工作。
不管是創建動態網站,還是創建桌面窗口應用程序,數據庫設計的重要性都不言而喻。
如果設計不當,查詢起來就非常吃力,程序的性能也會受到影響。
無論您是用的是SQL Server還是Oracle數據庫,通過進行規范化的數據庫設計,都可以使您的程序代碼更具有可讀性,更容易擴展,從而也會提升項目的應用性能。
2.1 什么是數據庫設計
數據庫設計就是規范和結構化數據庫中的數據對象以及這些數據對象之間關系的過程。
如圖是一個KTV數據庫的結構,示例該數據庫包含歌手、歌手類型、歌曲、歌曲分類的信息,圖中還顯示了各個對象之間的關系。
2.2 設計數據庫非常重要
數據庫中創建的數據結構的種類,以及在數據對象之間建立的復雜關系是數據庫系統效率的重要決定因素。
糟糕的數據庫設計表現為以下幾點:
- 效率低下
- 更新和檢索數據時會出現許多問題
良好的數據庫設計表現為以下幾點:
- 效率高
- 便於進一步擴展
- 應用程序開發更容易
3. 設計數據庫的步驟
經過了SQL Server基礎的學習,我們對項目的開發有了一個整體的感性認識,項目開發需要經過需求分析、概要設計、詳細設計、代碼編寫、運行測試和打包發布幾個階段。
重點討論在各個階段,數據庫的設計過程:
- 需求分析階段:分析客戶的業務和數據處理需求
- 概要設計階段:繪制數據庫的E-R模型圖,用於在項目團隊內部、設計人員和客戶之間進行溝通,確認需求信息的正確和完整
- 詳細設計階段:將E-R圖轉換為多張表,進行邏輯設計,確認各表主外鍵,並應用數據庫設計的三大范式進行審核。經項目組開會討論確定后,還需要根據項目的技術實現、團隊開發能力以及項目的經費來源,選擇具體的數據庫(如SQL Server或Oracle等)進行物理實現,包括建庫、建表並創建我們后面學習的存儲過程和觸發器等。創建完畢后開始代碼編寫階段,開發前端應用程序。
現在,我們共同討論:在需求分析階段,后台數據庫的設計步驟。
- 需求分析階段的重點是調查、收集並分析客戶業務數據需求、處理需求、安全性與完整性需求。
- 常用的需求調查方法有:在客戶的公司跟班實習、組織召開調查會、邀請專人介紹、設計調查表並請用戶填寫、查閱業務相關數據記錄等。
- 常用的數據分析方法有:調查客戶的公司組織情況、各部門的業務需求情況、協助客戶分析系統的各種業務需求、確定新系統的邊界。
無論數據庫的大小和復雜程度如何,再進行數據庫的系統分析時,都可以參考下列幾本步驟:
- 收集信息
- 標識對象
- 標識沒個對象需要存儲的詳細信息
- 標識對象之間的關系
3.1. 收集信息
創建數據庫之前,必須充分理解數據庫需要完成的任務和功能。
簡單的說,我們需要了解數據庫需要存儲哪些信息(數據),實現哪些功能。
以BBS論壇系統為例,我們需要了解BBS論壇的具體功能,與后台數據庫的關系:
- 用戶注冊和登錄:后台數據庫需要存放用戶的注冊信息和在線狀態信息
- 用戶發帖:后台數據庫需要存放帖子相關信息,如帖子內容、標題等
- 論壇版塊管理:后台數據庫需要存放各個板塊信息,如版主、板塊名稱和帖子數量等
3.2 標識對象(實體)
在收集需求信息后,必須標識數據庫要管理的關鍵對象或實體。
我們曾在Java中學習過對象的概念,對象可以是有形的事務,如人或產品。也可以是無形的事務,如商業交易、公司部門或發薪周期。
在系統中標識這些對象以后,與他們相關的對象就會理清楚。
以BBS論壇系統為例,我們需要標識出系統中的主要對象(實體),注意:對象一般是名詞,一個對象只描述一件事情,不能重復出現含義相同的對象:
- 論壇用戶:包括論壇普通發帖、回帖用戶、各板塊的版主
- 帖子:用戶發的帖子或是回的帖子
- 板塊:論壇的各個板塊信息
數據庫中的每個不同的對象都擁有一個與其相對應的表,也就是說,在我們的數據庫中,會對應至少三張表,分別是用戶表、帖子表和板塊表。
3.3 標識每個對象需要存儲的詳細信息(屬性)
將數據庫中的主要對象標識為表的候選對象以后,下一步就是標識每個對象存儲的詳細信息,也稱為該對象的屬性,這些屬性將組成表中的列。
簡單的說,就是需要細分出每個對象包含的子成員信息。
以BBS論壇系統為例,我們逐步分解每個對象的子成員信息,在分解時又發現發帖合回帖不同,所以把帖子細分為發帖合回帖兩個對象(實體)。
注意:
分解時,含義相同的成員信息不能重復出現,例如聯系方式和電話等,每個對象對應一張表,對象中的每個子成員對應表中的每一列。
例如,從上述的關系就可以看出用戶表應該包含列:用戶名、密碼和電子郵件等
3.4 標識對象(實體)之間的關系
關系型數據庫有一項非常強大的功能,它能夠關聯數據庫中各個項目的相關信息。
不同類型的信息可以單獨存儲,但是如果需要,數據庫引擎可以根據需求將數據組合起來。
在設計過程中,要標識對象之間的關系,需要分析這些表,確定這些表在邏輯上是如何相關聯的。
以及添加關系列建立起表之間的連接。
以BBS論壇系統為例:
- 發帖合回帖有主從關系,我們需要在回帖對象中生命它是誰的回帖
- 板塊管理中的版主合論壇用戶有關系,從用戶對象中可以根據板塊對象查出對應的版主用戶的情況
- 板塊合發帖有主從關系,需要聲明發帖是屬於哪個板塊的
- 板塊也和回帖有主從關系,需要聲明回帖是屬於哪個板塊的
4. 繪制E-R(實體-關系)圖
在需求分析階段解決了客戶的業務合數據處理需求后,就進入了我們的概要設計階段,我們需要合項目團隊的其他成員以及我們的客戶溝通,討論數據庫的設計是否滿足客戶的業務合數據處理需求。
和機械行業需要機械制圖,建築行業需要施工圖一樣,我們的數據庫設計也需要圖形化的表達方式——E-R(Entity-Relationship)實體關系圖,它也包括一些具有特定含義的圖形符號。
下面將介紹相關理論和具體的圖形符號。
4.1 實體-關系模型
4.1.1 實體
所謂實體就是指現實世界中具有區分其它事物的特征或屬性並與其它實體有聯系的對象。
例如BBS論壇系統中的用戶、帖子、板塊等,實體一般是名詞,它對應我們表中的一行數據,例如張三用戶這個實體,將對應“用戶表”中,張三用戶所在的一行數據,包括他的密碼、出生日期、電子郵件等信息。
嚴格的說,實體指表中一行一行的特定數據,但我們在開發時,也常常把整個表也稱為一個實體。
4.1.2 屬性
屬性可以理解為實體的特征。
例如:“用戶”這個實體的屬性有昵稱、出生日期和電子郵件等。
屬性對應表中的列。
4.1.3 關系
關系是兩個貨多個實體之間的聯系。
如圖是用戶實體和板塊實體之間的關系,實體使用方塊表示,實體一般是名詞,屬性使用橢圓表示,一般也是名詞。
關系使用菱形表示,一般是動詞。
4.1.4 映射基數
映射基數表示可以通過關系與該實體關聯的其它實體的個數。
對於實體集X和Y之間的二元關系,映射基數必須為下列基數之一:
- 一對一:X中的一個實體最多與Y中的一個實體關聯,並且Y中的一個實體最多與X中的一個實體關聯。假定規定一個論壇用戶只能擔任一個板塊的版主,那么,用戶實體和板塊實體之間就是一對一關系。
- 一對多:X中的一個實體可以與Y中的任意數量的實體關聯。Y中的一個實體最多與X中的一個實體關聯。一個發帖可以有多個回帖,所以說,發帖實體和回帖實體之間就是典型的一對多關系,一對多關系也常表示為1:N
- 多對一:X中的一個實體最多與Y中的一個實體關聯。Y中的一個實體可以與X中的任意數量的實體關聯。發帖實體和回帖實體之間就是典型的一對多關系,反過來說,回帖實體和發帖實體之間就是多對一關系了
- 多對多:X中的一個實體可以與Y中的任意數浪的實體關聯,反之亦然。假定一個板塊允許有多個版主,一個用戶也允許擔任多個板塊的版主,那么板塊實體和用戶實體之間就是典型的多對多關系了,多對多關系也常用符號表示為M:N
4.1.5 實體關系圖
E-R圖以徒刑的方式將數據庫的這個那個邏輯結構表示出來,E-R圖的組成包括:
- 矩形表示實體
- 橢圓表示屬性
- 菱形表示關系集
- 直線用來連接屬性和實體集,也用來連接實體集和關系集
在本教程中,直線可以是有方向的(在末端有一個箭頭),用來表示關系集的映射基數。
如圖顯示了一些示例,這些示例表示了可以通過關系與一個實體相關聯的其它實體的個數,箭頭的定位很簡單,可以將其視為指向引用的實體。
- 1:1——每個論壇用戶只能管理一個板塊,並且每個板塊也只能由一個用戶擔任
- 1:N——每個論壇用戶可以發表多個帖子,但是一個帖子只能對應一個發帖用戶
- M:N——上圖中沒有體現,但是現實中,比如每個郵箱可以注冊多個賬戶等都是多對多關系
繪制E-R圖后,我們還需要與客戶反復進行溝通,讓客戶提出修改意見,以確認系統中數據處理需求是否表示的正確和完整。
4.2 如何將E-R圖轉換為表
該要設計解決了客戶的需求捕獲,並繪制了E-R圖,在后續的詳細設計階段,我們需要把E-R圖轉換為多張表,並標識各表的主外鍵。
下面將介紹如何將介紹如何將E-R圖轉換為表哥,如何審核各表的結構是否規范將在本篇最后進行介紹。
- 第一步,將各實體轉化為對應的表,將各屬性轉換為各表對應的列
- 第二步,標識每個表的主鍵列,需要注意的是:對沒有主見的表添加ID編號列,沒有實際含義,只用做主鍵或外鍵,例如用戶表中的UID列,板塊表中添加的SID列,發帖表和回帖表中的TID列。為了數據編碼的兼容性,建議實用英文字段。為了直觀可見,在英文括號內著名對應的中文含義。
- 第三步,我們還需要在表之間體現實體之間的映射關系
- (1) 板塊(BBSSession)表中的版主來自用戶(BBSUser)表中的個別用戶,所以她們之間建立主外鍵關系,板塊(BBSSession)表中引用的版主,應引用用戶表中的用戶編號(UID),體現一對一的關系
- (2) 同理,發表帖(BBSTopic)和回帖表(BBSReply)中的發帖人也應和用戶表中的用戶編號(UID)建立主外鍵關系,體現一對多的關系
- (3) 回帖表(BBSReply)中的“回復的主帖”對應主貼表(BBSTopic)的“標識主鍵”列(TID),建立主外鍵關系
- (4) 回帖表(BBSReply)和主帖表(BBSTopic)中的“所在板塊”列也應該與板塊表(BBSSession)中板塊編號(SID)建立主外鍵關系。
根據上述E-R圖轉換的表如圖:
標識各表鍵的關系后如圖:
5. 數據規范化
5.1 設計問題
有人開玩笑說,在該要設計階段,同一個項目,10個設計人員將設計出10種不同的E-R圖。
不錯,不同的人從不同的角度,標識出不同的實體,實體又飽含不同的屬性,自然就設計出不同的E-R圖。
那么怎樣審核這些設計圖呢?怎么評審出最優秀的設計方案呢?
所以,我們的下一步工作就是規范化E-R圖了。
為了討論方便,下面直接以賬戶表(Account)為例,該表存儲有關銀行客戶賬戶的信息和交易細節:
帳號 | 客戶姓名 | 地址 | 開戶日期 | 賬戶類型 | 交易號 | 交易金額 | 交易日期 |
85001 | Smith | 1,Main | 1/4/03 | Savings | 1 | 1000 | 2/4/03 |
85002 | James | 2,Main | 3/4/03 | Current | 2 | 200 | 4/4/03 |
85003 | Ritcha | 3,Main | 4/4/03 | Savings | 3 | 100 | 5/4/04 |
86003 | Ritcha | 3,Main | 4/4/03 | Savings | 4 | 400 | 6/4/03 |
從用戶的角度而言,將所有信息放在一個表中很方便,因為這樣查詢數據庫可能會比較容易,但是上述表具有下列問題:
- 信息重復
有很多信息是重復的,“客戶姓名”和“賬戶類型”列種有許多重復信息,例如“Savings”,信息重復會造成存儲空間的浪費以及一些其它問題,如果不小心輸入“Saving”和“Savings”,在數據庫中將會表示不同的賬戶類型。 - 更新異常
冗余信息不僅浪費存儲空間,而且會增加更新的難度。如果需要修改表示將儲蓄帳戶指定為“Savings Account”而不是“Savings”的測試,則需要修改所有飽含該值的行,則數據庫中酒會又兩種類型的儲蓄帳戶,一個是“Savings”,另一個是“Savings Account”,這種情況被稱為更新異常。 - 插入異常(無法表示某些信息)
上述表中,帳號或交易號單獨不能作為主鍵,我們需要采用組合鍵作為主鍵,假設上表的主鍵為(帳號,交易號),任何要插入到該關系中的新行必須提供主鍵的值,因為鮮有的完整性要求主鍵不能完全或部分為空。如果某個帳號希望開戶,即向該表中插入一行數據,如果帳號剛開戶,還沒有交易記錄,您將無法插入開戶信息的數據。這種問題被稱為插入異常。 - 刪除異常(丟失有用的信息)
在某些情況下,當刪除一行時,可能會丟失有用的信息。例如,如果刪除帳號為85002的行,就會丟失賬戶類型為“Current”的賬戶信息,該表只剩下唯一的一種賬戶類型“Savings”了,當希望查詢有哪些賬戶類型時,將會誤以為只有“Savings”賬戶類型。這種情況被稱為刪除異常。
5.2 規范設計
如何重新規范設計上述表呢?如何避免上述諸多異常呢?
在數據庫的設計時,有一些專門的規則,成為數據庫的設計范式,遵守這些規則,您將創建設計良好的數據庫,下面將注意講解數據庫設計中著名的三大范式理論。
1. 第一范式(1NF,Normal Formate)
第一范式的目標時確保每列的原子性:如果每列(或者每個屬性值)都是不可再分的最小數據單元(也稱為最小的原子但願),則滿足第一范式(1NF)。
例如:
顧客表(顧客編號、地址、...),其中地址列還可以細分為國家、省、市、區等,更多的程序甚至把姓名也拆分為姓和名等。
2. 第二范式(2NF)
第二范式在第一范式的基礎上,更進一層,其目標是確保表中的每列都和主鍵相關,如果一個關系滿足1NF,並且出了主鍵以外的其它列,都依賴於該主鍵,則滿足第二范式(2NF)。
例如:
訂單表(訂單編號、產品編號、訂購日期、價格、...)
該表主要用來描述訂單,所以訂單編號設為主鍵,“訂購日期”、“價格”兩列都和“訂單編號”主鍵相關,但“產品編號”列和“訂單編號”列沒有直接關系,即“產品編號”列不依賴於“訂單編號”主鍵列,該列應從該表中刪除,放入產品表中。
這樣,該表就只描述一件事情:訂單信息。
3. 第三范式(3NF)
第三范式在第二范式的基礎上,更進一層,第三范式的目標是確保每列都和主鍵列直接相關,而不是間接相關:如果一個關系滿足2NF,並且出了主鍵以外的其它列都不依賴於主鍵列,則滿足第三范式(3NF)。
為了理解第三范式,需要根據Armstrong公理之一定義傳遞依賴:假設A、B和C是關系R的3個屬性,如果A->B且B->C,則從這些函數依賴(FD)中,可以得出A->C,如上所述,依賴A->C是傳遞依賴。
例如:
訂單表(訂單編號、訂購日期、顧客編號、顧客姓名、...)
初看該表沒有問題,滿足2NF,每列都和主鍵列“訂單編號”相關,再細看您會發現“顧客姓名”列和“顧客編號”相關,“顧客編號”列和“訂單編號”又相關,最后經過傳遞依賴,“顧客姓名”也和“訂單編號”相關。
為了滿足3NF我們應該去掉“顧客姓名”列,將此列放入客戶表中。
了解了用於規范化數據庫設計的三大范式之后,咱們回頭看一下上面那個Account帳號表。
1.是否滿足第一范式
第一范式要求每列必須是最小的原子單元,即不能再細分。
前面我們提及過,地址需要氛圍省、市、區等,方便查詢。但我們沒有這方面的查詢需求,所以本例中沒必要拆分“地址”列。
所以,該表滿足第一范式。
2.是否滿足第二范式
第二范式要求每列必須和主鍵相關,不想管的列放入別的表中,即要求一個表只描述一件事情。
實用的技巧是,我們可以直接查看該表描述了哪幾件事情,然后一件事情創建一張表。經過分析,該表描述了三件事情:
1) 賬號信息
2) 交易信息
3) 賬戶類型信息
即我們需要創建三張表,對各列進行篩選,拆分后結果表如下:
類型編號 | 賬戶類型 |
1 | Savings |
2 | Current |
交易號 | 交易金額 | 交易日期 | 帳號 |
1 | 1000 | 2/4/03 | 85001 |
2 | 200 | 4/4/03 | 85002 |
3 | 100 | 5/4/03 | 85003 |
4 | 400 | 6/4/03 | 86003 |
帳號 | 客戶信息 | 地址 | 開戶日期 | 賬戶類型 |
85001 | Smith | 1,Main | 1/4/03 | 1 |
85002 | James | 2,Main | 3/4/03 | 2 |
85003 | Ritcha | 3,Main | 4/4/03 | 1 |
86003 | Ritcha | 4,Main | 4/4/03 | 1 |
3.是否滿足第三范式
第三范式要求該表中各列必須和主鍵直接相關,不能間接相關,瀏覽每個表,已經滿足了。
5.3 規范化和性能的關系
需要提醒的是,對於項目的最終用戶來說,客戶最關系的是方便、清晰的數據結果。
您如果讓客戶選擇,毫無疑問,客戶會認為最初的表設計最適合需求,雖然他根本就不滿足三大方式,並且存在大量的數據冗余。
所以說,我們在設計數據庫時,設計人員和客戶對數據庫的設計有一定的矛盾。
通過三大范式分解的三張表,為了滿足客戶的需求,最終我們需要通過三張表之間的連接查詢,恢復為客戶需要的數據結果。
插入數據同樣如此,對客戶輸入的數據,我們需要分開插入在三張不同的表中。
由此可以看出,為了滿足三大范式,我們的蘇劇操作性可能會收到相應的影響。
所以,在時機的數據庫設計中,既要考慮三大范式,避免數據的冗余和各種數據操作異常,還要考慮數據訪問性能。
有時,為了減少表間鏈接,提高數據庫的訪問性能,適當允許少量數據的冗余列,才是最合適的數據庫設計方案。
6. 總結
在需求分析階段,設計數據庫的一般步驟如下:
- 收集信息
- 標識對象
- 標識每個對象的屬性
- 標識對象之間的關系
在概要設計階段和相惜設計階段,設計數據庫的一般步驟為如下:
- 繪制E-R圖
- 將E-R圖轉換為表格
- 應用三大范式規范化表哥
從關系型數據庫中除去冗余數據的過程稱為規范化。如果使用得當,規范化是用於獲得高效關系型數據庫中表的邏輯結構的最好和最容易的方法。
規范化數據時,執行下列操作:
- 將數據庫的結構精簡為最簡單的形式
- 從表中刪除冗余的列
- 標識所有依賴於其他數據的數據