文是一個參考,不是一個規範,更不是一個標準。它僅代表了我個人的觀點和建議,並只考慮了通常條件下的規則,你可以根據實際情況隨意修改它。
引言
編碼規範是一個優秀程式員的必備素質,然而,有很多人非常注重程式中變數、方法、類的命名,卻忽視了同樣重要的資料庫物件命名。這篇文章結合許多技術文章和資料,以及我自己的開發經驗,對資料庫物件的命名規則提出了一點建議,希望能為大家提供一些參考。
NOTE:雖然這篇文章名為“資料庫物件命名參考”,實際上,在這篇文章中我不僅介紹了資料庫命名的規則,連帶講述了在資料庫設計與開發時所需要注意的幾個問題。 基本命名規則
表1. 基本資料庫物件命名
| 資料庫物件 |
首碼 |
舉例 |
表(Table)
欄位(Column)
視圖(View)
預存程序(Stored procedure)
觸發器(Trigger)
索引(Index)
主鍵(Primary key)
外鍵(Foreign key)
Check約束(Check Constraint)
Unique約束
使用者定義資料類型(User-defined data type)
使用者定義函數(User-defined function) |
無
無
v
pr
tr
ix_
pk_
fk_
ck_
uq_
udt
fn |
Student
Title
vActivity
prDelOrder
trOrder_D
ix_CustomerID
pk_Admin
fk_Order_OrderType
ck_TableColumn
uq_TableColumn
udtPhone
fnDueDate |
關於命名的約定
變數(T-SQL編程中聲明的變數)、過程(預存程序或觸發器等)、實體(表、欄位)應該根據他們所代表的實體意義和進程作用來命名:
表2.好的命名 和 不好的命名 範例
| 好的命名 |
不好的命名 |
@CurrentDate
@ActivityCount
@EquipmentType
prCalculateTotalPrice |
@D
@ActNum
@ET
@prRunCalc |
還有一個常見的錯誤就是只使用面向電腦的術語,而不是面向公司業務的術語,比如ProcessRecord就是一個含糊不清的命名,應該使用一個進程業務描述來替換它,比如CompleteOrder.
如果完全根據上一條的要求,那麼根據業務描述的過程名可能會變得很冗長,比如下面:
prCountTotalAmountOfMonthlyPayments (計算每月付費的總金額)
prGetParentOrganizationalUnitName (擷取上層客戶名稱)
此時則應該考慮使用縮寫: 如果可以在字典裡找到一個詞的縮寫,就用這個做為縮寫,比如:Mon(Monday)、Dec(December) 可以刪除單詞母音(詞首字母除外)和每個單詞的重複字母來縮寫一個單詞。比如:Current = Crnt、Address = Adr、Error = Err、Average = Avg 不要使用有歧異的縮寫(一般是語音上的歧義)。比如b4(before)、xqt(execute),4tran(Fortran) 表格、欄位的命名: 單數表名、欄位名 還是 複數表名、欄位名。
可能大家很少會考慮到給表名起單數還是複數,比如,對儲存客人資訊的表,我們應該起Customer,還是Customers。我主張起單數表名,下面是來自《SQL Server 2000 寶典》的一段引用:
主張用複數表名的陣營認為:表是由一組記錄構成的,所以應當使用複數名詞來命名它。他們經常使用的理由是:客戶表是客戶們的集合,而集合意味著多個,因此應當稱他們為Customers表。除非你只有一個客戶,但這種情況你根本用不著資料庫。
根據筆者的非正式調查,有3/4的SQL Server開發人員支援使用單數命名。這些開發人員認為,客戶表是客戶的集合,而不是客戶們的集合。一組行不應當也不會被成為rows set(行們的集合),而會被稱為row set(行集)。並且,通常在討論時人們會使用單數名稱來稱呼表,說Customer表比說Customers表聽起來更為清晰。 避免無謂的表格尾碼
這兩點我想大家都知道:1、表是用來儲存資料資訊的。2、表是行的集合。那麼如果表名已經能夠很好地說明其包含的資料資訊,就不需要再添加體現上面兩點的尾碼了。
實際工作中,我看到有的同事對錶這樣命名:GuestInfo,用於儲存客戶資訊。這個命名與上面所說的第1點重複,誰都知道表本來就是儲存資訊(information)的,再加個Info無異於畫蛇添足,個人認為直接用Guest做表名就可以了。
對於儲存航班資訊的表,他又命名為FlightList。這個命名又與之前說的第2點相重複,表是行的集合,那麼自然是列表(List),加上List尾碼顯得很多餘,命名為 Flight 不是很好麼。可見,他給自己都沒有訂立一個明確的命名規則,不然這兩個表一定是要麼命名為:GuestList、FlightList 要麼命名為 GuestInfo、FlightInfo,而不會是兩者的混合。 多對多關係中串連表的命名
大家知道,如果要實現兩個實體間的多對多關係,需要三張表,其中一張是解析表。考慮下面這樣一個多對多關係,這是一個經典的學生選課問題:一個學生可以選很多門課,一門課可以有很多學生。此時為了實現上面的關係,就需要一張解析表(這張表只儲存學生ID和課程ID,而學生的資訊和課程資訊分別存在各自的表中),這個表的起名,建議的寫法是將兩個表的表名合并(如果表名比較長可做簡化),此處如 StudentCourse。這個表中欄位分別命名為StudentId、CourseID(既是此表的複合主鍵,同時分別為串連Student表和Course表的外鍵,等下到主鍵和外鍵的命名處再說),這樣就實現了學生和課程之間的多對多關係,當然,這個關係還可以加點額外的東西,比如給StudentCourse表中加AccessLevel欄位,範圍D{唯讀,完全,禁止},就可以實現存取層級。 約定俗成的欄位名前/尾碼
資料庫開發的時間久了,慢慢就會摸索出一個規律來:就是很多的欄位都有些共同的特性。比如說,有的欄位是代表時間的(例如發帖時間,評論時間),有的是代表數量的(例如瀏覽數,評論數),有的是代表真假類型的(例如是否將部落格隨筆顯示在首頁)。對於這種同一類型的欄位,應該使用統一的 首碼 或者 尾碼去標識它。
我們來舉幾個例子看得更明白一點。
以大家都熟悉的論壇來說,需要記錄會員最後一次登入的時間,這時候一般人都會把這個欄位命名為LoginTime 或者 LoginDate。這時候,已經產生了一個歧義:對於另一名開發人員來說,如果僅看錶的欄位名稱,不去看錶的內容,很容易將LoginTime理解成 登入的次數,因為,Time還有一個很常用的意思,就是次數。
為了避免這種情況發生,應該明確的規定:所有表示時間的欄位,統一以 Date 來作為結尾。
我們經常需要統計發帖數、回帖數資訊,這時候,開發人員通常會這樣去命名欄位:PostAmount、PostTime、PostCount,同樣,由於Time的歧義,我們首先排除掉不使用PostTime作為欄位名。接下來,Amount 和 Count 都可以表示計數的意思,用哪個合適呢。這裡,我推薦使用Count。為什麼呢。如果你做過Asp開發,相信一定知道 RecordCount 這個屬性,命名的時候有一個原則:就是使用約定俗成的名稱,而不要去自創名稱。既然微軟都用Count尾碼來表示數目,我們為什麼不呢。
於是,所有表示數目的欄位,都應該以Count作為結尾。將這一概念做以推廣,很容易得出,瀏覽次數為 ViewCount,登入次數為LoginCount 等等。
再舉一個例子,我們很少在資料庫裡直接儲存圖片等位元據,通常是僅儲存圖片的URL路徑;在文章管理系統中,如果是轉載文章,也會用到記錄文章出處的欄位。個人建議所有代錶鏈接的欄位,均為Url結尾。於是,圖片路徑的欄位命名為 ImageUrl,文章出處欄位的命名為SourceUrl。
最後一個例子,我們經常需要用到布爾值,比方說,這篇隨筆要不要顯示到首頁,這篇隨筆是不是儲存到草稿箱等等。同樣,按照微軟的建議,布爾類型的值均以 Is、Has 或者 Can開頭。
如果讓我來建表示是否將隨筆放到首頁的欄位,它的名字一定是這樣的:IsOnIndex
類似的例子是很多的,我在這裡僅舉出典型的幾個範例,大家可以自行拓展,如果我能起到一個拋磚引玉的作用就很滿足了。 欄位命名時需注意的一個問題
我發現有很多開發人員喜歡給欄位加上表名作為它的首碼,舉個例子,如果有個表叫User,那麼他就會將這個表中的欄位命名為:UserId、UserPassword、UserName、UserPhone 等等。個人認為,這是沒有必要的,因為你已經確切的知道了這個表格儲存體的是User的資訊,那麼其中的欄位必然是針對於User的。而且,在Join串連操作中,你的SQL代碼看上去也會更加的精簡一些,諸如 [User].UserName = Aritcle.ArticleAuthor 這樣的代碼完全可以實現為 [User].Name = Article.Author。
這裡還存在一個特例,就是表的外鍵包含的欄位。在這種情況下,我傾向於使用表名+ID 的方式,比如 CategoryId 、UserId 等。假設有表Article,那麼它的主鍵我會命名為Id,關聯使用者表User的外鍵包含的欄位,我會命名為UserId。之所以這樣,是因為在語言(比如C#)中建立對象時,有時候會使用代碼產生器(根據資料庫的欄位名產生對象的欄位、屬性名稱),此時產生的程式碼更規整一些。 建表時需要注意的問題 資料庫不僅是用來儲存資料,還應負責維護資料的完整性和一致性
我看過很多的開發人員設計出來的資料庫,給我的感覺就是:在他們眼裡,資料庫的作用就如同它的名稱一樣――僅僅是用來存放資料的,除了不得不建的主鍵以外,什麼都沒有...沒有 Check約束,沒有索引,沒有外鍵約束,沒有視圖,甚至沒有預存程序。
在這裡,我提出如下資料庫設計的建議: 如果要寫代碼來確保表中的行都是唯一的,就為表添加一個主鍵。 如果要寫代碼來確保表中的一個單獨的列是唯一的,就為表添加一個約束。 如果要寫代碼確定表中的列的取值只能屬於某個範圍,就添加一個Check約束。 如果要寫代碼來串連 父-子 表,就建立一個關係。 如果要寫代碼來維護“一旦父表中的一行發生變化,連帶變更子表中的相關行”,就啟用串聯刪除和更新。 如果要調用大量的Join來進行一個查詢,就建立一個視圖。 如果要逐條的寫資料庫操作的語句來完成一個商務規則,就使用預存程序。
NOTE:這裡我沒有提到觸發器,實踐證明觸發器會使資料庫迅速變得過於複雜,更重要的是觸發器難以調試,如果不小心建了個連環觸發器,就更讓人頭疼了,所以我更傾向於根本就不使用觸發器。 以Not Null的思路建表
我發現很多開發人員在建表的時候,如果要建立一個欄位,他的思路是這樣的:預設這個欄位是可以為Null的,然後去判斷是不是非要Not Null不可,如果不是這樣,OK,這個欄位可以為Null,接著繼續進行下一個欄位。結果往往是一張表除了主鍵以外所有的欄位都可以為Null。
之所以會有這樣的思路,是因為Null好啊,程式不容易出錯啊,你插入記錄的時候如果不小心忘輸了一個欄位,程式依然可以Run,而不會出現 “XX欄位不能為Null”的錯誤訊息。
但是,這樣做的結果卻是很嚴重的,也會使你的程式變得更加繁瑣,你不得不進行一些無謂的空值處理,以避免程式出錯。更糟的是,如果一些重要資料,比如說訂單的某一項值為Null了,那麼大家知道,任何值與Null相操作(比如加減乘除),結果都是Null,導致的結果就是訂單的總金額也為Null。
你可以運行下面的代碼嘗試一下:
Select Null + 5 As Result
你可能會說,就算我將欄位設定成Not Null,但是它依然可以接受Null 字元串,這樣一來在程式中還是要進行空值處理。請別忘了,資料庫還賦予你一個強力武器,就是 Check 約束,當你需要確保一個欄位既不可以為Null,又不可以為空白的時候,可以這麼寫:
ColumnName Varchar(50) Not Null Constraint ck_ColumnName Check(Len(ColumnName) > 0)
所以,合理的思維方式應該是這樣的:預設這個欄位是 Not Null的,然後判斷這個欄位是不是非為Null不可,如果不是這樣,OK,這個欄位是Not Null的,進行下一個欄位。 一個建表的範例指令碼
我正在建立我自己的個人空間,其中的文章表是這樣寫的:
Create Table Article
(
Id Int Identity(1,1) Not Null,
Title Varchar(50) Not Null Constraint uq_ArticleTitle Unique,
Keywords Varchar(50) Not Null,
Abstract Varchar(500) Not Null,
Author Varchar(50) Not Null Default '張子陽',
Type TinyInt Not Null Default 0 Constraint ck_ArticleType Check(Type in (0,1,2)), -- 0,原創;1,編譯;2,翻譯
IsOnIndex Bit Not Null Default 1, -- 是否顯示在首頁
Content Text Not Null,
SourceCode Varchar(100) Null, -- 程式源碼的下載路徑
Source Varchar(50) Not Null Default 'TraceFact', -- 文章出處
SrcUrl Varchar(150) Null, -- 文章出處的URL
PostDate DateTime Not Null Default GetDate(),
ViewCount Int Not Null Default 0,
ClassId Int Not Null -- 外鍵包含的欄位,文章類別
Constraint pk_Article Primary Key(Id) -- 建立主鍵
)
可以看到,在這裡我使用了 Check 約束,以確保文章類型只能為 0,1,2。這裡,我想說的是Check 約束的命名規則:儘管Check約束是針對欄位的,但在同一資料庫中,卻不能有同名的Check約束。所以,建議使用 ck_ + 表名 + 欄位名 來命名它,比如這個範例指令碼中的 ck_ArticleType。
除此以外,我還使用了Unique約束,以確保文章標題的唯一性。由於這是我的部落格文章表,不應該出現重複的題目,這樣可以避免在使用 Insert 語句時插入重複值。類似於Check約束,這裡的命名規則是:uq_ + 表名 + 欄位名。 主鍵的命名
按照SQL Server 的預設規範(使用企業管理器建立主鍵時預設產生的主鍵名),主鍵的命名為 pk_TableName。主鍵是針對一個表的,而不是針對一個欄位的,大家有時候在企業管理器中會見到一個表的兩個欄位前面都會有鑰匙的表徵圖(比如SQL Server 2000內建的NorthWind範例資料庫的EmployeeTerritories表),就會誤以為主鍵是針對欄位的,即是說一個表上有兩個主鍵,其實錯了,只有一個主鍵,但包含了兩個欄位,這就是常說的複合主鍵。為了有個更生動的認識,看下建立複合主鍵的SQL語句,以上面說到的多對多串連表StudentCourse為例:
Alter Table StudentCourse
Add Constraint pk_StudentCourse Primary key(StudentId, CourseId)
可見,對於主鍵pk_StudentCourse,包含了兩個欄位StudentId 和 CourseId。 外鍵的命名
外鍵的命名為 fk_外鍵所在的表名_外鍵引用的表名。因為外鍵所在的表為從表,所以上式可以寫為 fk_從表名_主表名。
外鍵包含的欄位的命名,外鍵包含的欄位和外鍵是完全不同的概念。外鍵包含欄位的命名,建議為:外鍵所在的表名 + Id。
考慮這樣一個關係,表Hotel,欄位Id, Name, CityId。表City,欄位Id,Name。因為一個城市可能有好多家酒店,所以是一個一對多的關係,City是主表(1方),Hotel是從表(多方)。在Hotel表中,CityId是做為外鍵使用。
在實現外鍵的時候我們可以這樣寫:
Alter Table HotelInfo
Add Constraint fk_HotelInfo_City Foreign Key (CityID) References City(ID)
On Delete No Action On update No Action
很明顯,fk_HotelInfo_City是外鍵的名字,CityId是外鍵包含的欄位的名字。
NOTE:在建立資料庫表的時候,一般需要寫成三個SQL指令檔。第一個檔案僅包含所有的建立表的SQL語句,即Create Table 語句。第二個檔案包含刪除關係和表的語句,其中,所有刪除關係的語句,即Drop Constraint 語句集中在這個檔案的上半部分,所有刪除表的語句,Drop Table語句,集中在這個檔案的下半部分。第三個檔案包含建立表之間關係的語句。這種做法會在你移植資料庫的時候產生較大的便利,原因我就不解釋了,您一試便知。
而對於多對多關係中解析表的外鍵包含的欄位,順理往下推,我們可以這樣寫(再次回到學生選課的多對多例子中):
建立解析表StudentCourse與Student表的外鍵關係:
Alter Table StudentCourse
Add Constraint fk_StudentCourse_Student Foreign Key (StudentId) References Student (Id)
On Delete No Action On Update No Action
建立解析表StudentCourse與Course 表的外鍵關係:
Alter Table StudentCourse
Add Constraint fk_StudentCourse_Course Foreign Key (CourseId) References Course(Id)
On Delete No Action On Update No Action 觸發器的命名
由三部分構成: 首碼(tr),描述了資料庫物件的類型。 基本部分,描述觸發器所加的表。 尾碼(_I、_U、_D),顯示了修改語句(Insert, Update及Delete) 預存程序的命名
大家知道,系統預存程序的首碼是 sp_,為了避免將使用者預存程序與系統預存程序混淆,這裡我推薦大家使用 pr 作為自己定義的預存程序的命名。
同時,命名的規則是:採用自解釋型的命名,比如:prGetItemById。
這裡,有個有意思的地方值得深思。我們按上面規則命名預存程序的時候,可以用兩種方式: 動詞放前面,名詞放後面。 名詞放前面,動詞放後面。
我個人推薦使用方式2,現在說說原因:
以NorthWind 為例,假如對於 Employees 表你有4個預存程序,分別命名為:prEmployeeInsert、prEmployeeUpdate、prEmployeeDelById、prEmployeeGetById
同時對於 Products 表你有類似的4個預存程序,分別命名為:prProductInsert、prProductUpdate、prProductDelById、prProductGetById
這時,你用企業管理器查看時,會發現預存程序像下面這樣整整齊齊的排列:
prEmployeeDelById
prEmployeeGetById
prEmployeeInsert
prEmployeeUpdate
prProductDelById
prProductGetById
prProductInsert
prProductUpdate
很容易就會發現,當你的預存程序越多時,這種命名方法的優勢就越明顯。 預存程序中參數的命名
預存程序中的入口參數,我建議與其對應的欄位名相同,這裡,假設要寫一個更新Northwind資料庫Employees表的預存程序(做了簡化),可以這麼寫:
Create Procedure prEmployeeUpdateById
@EmployeeId Int,
@LastName NVarchar(20),
@FirstName NVarchar(10)
As
Update Employees Set
LastName = @LastName,
FirstName = @FirstName
Where
EmployeeId = @EmployeeId
If @@error <> 0 or @@RowCount = 0
Raiserror 16001 ‘更新使用者失敗’ 總結
在這篇文章中,我首先提出了開發人員對資料庫物件命名不夠重視的問題,隨後列出了一張資料對象命名的簡表。
接著我按照 表、欄位、主鍵、外鍵、觸發器、預存程序的順序,詳細講述了資料庫物件命名的規則。
其間,我還穿插著講述了在資料庫開發中常見的一些問題,包括建表時需要注意的問題,以及在管理預存程序時可以採取的技巧。
希望這篇文章能給你帶來協助。