Brian Walker
可能讓人覺(jué)得很奇怪,但好像的確沒(méi)有什么“正式的”T-SQL 編碼標(biāo)準(zhǔn)。早在 1999 年末的時(shí)候,我驚喜地發(fā)現(xiàn) John Hindmarsh 提出的 SQL Server 7.0 標(biāo)準(zhǔn),我在 2000 年 2 月的社論中對(duì)他的某些建議進(jìn)行了總結(jié)。(2000 年 2 月以及本月的“下載”中都包括了 John 原來(lái)的標(biāo)準(zhǔn)。)后來(lái),Ron Talmage 撰寫(xiě)了一系列專(zhuān)欄文章,提出了他對(duì)各種“最佳方法”的建議,當(dāng)然,SQL Server 小組也已正式發(fā)布了 SQL Server 最佳方法分析器 (SQLBPA)?,F(xiàn)在,一位具有超過(guò) 25 年經(jīng)驗(yàn)的數(shù)據(jù)庫(kù)管理員和應(yīng)用程序開(kāi)發(fā)員 Brian Walker 又提出了他的建議和提示。
進(jìn)行 T-SQL 編程時(shí)常常會(huì)忽略編碼標(biāo)準(zhǔn),但這些標(biāo)準(zhǔn)卻是開(kāi)發(fā)小組順利開(kāi)展工作的關(guān)鍵工具。這里介紹的編碼標(biāo)準(zhǔn)是我多年的開(kāi)發(fā)成果。它們當(dāng)然還沒(méi)有得到普遍接受,而且不可否認(rèn),有些標(biāo)準(zhǔn)帶有主觀色彩。我的目的實(shí)際上更多的是為了提高大家的意識(shí),而不是吹捧自己是 T-SQL 樣式方面的仲裁者:最重要的是要建立某些合理的編碼標(biāo)準(zhǔn)并遵循這些標(biāo)準(zhǔn)。您在這篇文章中會(huì)發(fā)現(xiàn)有關(guān) T-SQL 編程的一系列不同的編碼標(biāo)準(zhǔn)、技巧和提示。它們并未以任何特定的優(yōu)先級(jí)或重要性順序列出。
讓我們從格式開(kāi)始。表面上,T-SQL 代碼的格式似乎并不重要,但一致的格式可以使您的同事(不論是同一小組的成員還是更大范圍的 T-SQL 開(kāi)發(fā)團(tuán)隊(duì)的成員)更輕松地瀏覽和理解您的代碼。T-SQL 語(yǔ)句有一個(gè)結(jié)構(gòu),遵循一目了然的結(jié)構(gòu)使您可以更輕松地查找和確認(rèn)語(yǔ)句的不同部分。統(tǒng)一的格式還使您可以更輕松地在復(fù)雜 T-SQL 語(yǔ)句中增刪代碼段,使調(diào)試工作變得更容易。下面是 SELECT 語(yǔ)句的格式示例:
SELECT C.Name, E.NameLast, E.NameFirst, E.Number, ISNULL(I.Description,‘NA‘) AS DescriptionFROM tblCompany AS CJOIN tblEmployee AS EON C.CompanyID = E.CompanyIDLEFT JOIN tblCoverage AS VON E.EmployeeID = V.EmployeeIDLEFT JOIN tblInsurance AS ION V.InsuranceID = I.InsuranceIDWHERE C.Name LIKE @NameAND V.CreateDate > CONVERT(smalldatetime,‘01/01/2000‘)ORDER BY C.Name, E.NameLast, E.NameFirst, E.Number, ISNULL(I.Description,‘NA‘)SELECT @Retain = @@ERROR, @Rows = @@ROWCOUNTIF @Status = 0 SET @Status = @Retain
一個(gè)嵌套代碼塊中的語(yǔ)句使用四個(gè)空格的縮進(jìn)。(上述代碼中的多行 SELECT 語(yǔ)句是一個(gè) SQL 語(yǔ)句。)在同一語(yǔ)句中開(kāi)始新行時(shí),使 SQL 關(guān)鍵字右對(duì)齊。將代碼編輯器配置為使用空格,而不是使用制表符。這樣,不管使用何種程序查看代碼,格式都是一致的。
大寫(xiě)所有的 T-SQL 關(guān)鍵字,包括 T-SQL 函數(shù)。變量名稱(chēng)及光標(biāo)名稱(chēng)使用混和大小寫(xiě)。數(shù)據(jù)類(lèi)型使用小寫(xiě)。
表名別名要簡(jiǎn)短,但意義要盡量明確。通常,使用大寫(xiě)的表名作為別名,使用 AS 關(guān)鍵字指定表或字段的別名。
當(dāng)一個(gè) T-SQL 語(yǔ)句中涉及到多個(gè)表時(shí),始終使用表名別名來(lái)限定字段名。這使其他人閱讀起來(lái)更清楚,避免了含義模糊的引用。
當(dāng)相關(guān)數(shù)字出現(xiàn)在連續(xù)的代碼行中時(shí)(例如一系列 SUBSTRING 函數(shù)調(diào)用),將它們排成列。這樣容易瀏覽數(shù)字列表。
使用一個(gè)(而不是兩個(gè))空行分隔 T-SQL 代碼的邏輯塊,只要需要就可以使用。
聲明 T-SQL 局部變量(例如 @lngTableID)時(shí),使用適當(dāng)?shù)臄?shù)據(jù)類(lèi)型聲明和一致的大寫(xiě)。
始終指定字符數(shù)據(jù)類(lèi)型的長(zhǎng)度,并確保允許用戶可能需要的最大字符數(shù),因?yàn)槌鲎畲箝L(zhǎng)度的字符會(huì)丟失。
始終指定十進(jìn)制數(shù)據(jù)類(lèi)型的精度和范圍,否則,將默認(rèn)為未指定精度和整數(shù)范圍。
使用錯(cuò)誤處理程序,但要記住行首 (BOL) 中的錯(cuò)誤檢查示例不會(huì)象介紹的那樣起作用。用來(lái)檢查 @@ERROR 系統(tǒng)函數(shù)的 T-SQL 語(yǔ)句 (IF) 實(shí)際上在進(jìn)程中清除了 @@ERROR 值,無(wú)法再捕獲除零之外的任何值。(即使示例起作用,它們也只能捕獲最后發(fā)生的一個(gè)錯(cuò)誤,而不是您更想捕獲的第一個(gè)錯(cuò)誤。)必須使用 SET 或 SELECT 立即捕獲錯(cuò)誤代碼,如前面示例所示。如果狀態(tài)變量仍然為零,應(yīng)轉(zhuǎn)換到狀態(tài)變量。
避免使用“未聲明的”功能,例如系統(tǒng)表中未聲明的列、T-SQL 語(yǔ)句中未聲明的功能或者未聲明的系統(tǒng)存儲(chǔ)過(guò)程或擴(kuò)展的存儲(chǔ)過(guò)程。
不要依賴任何隱式的數(shù)據(jù)類(lèi)型轉(zhuǎn)換。例如,不能為數(shù)字變量賦予字符值,而假定 T-SQL 會(huì)進(jìn)行必要的轉(zhuǎn)換。相反,在為變量賦值或比較值之前,應(yīng)使用適當(dāng)?shù)?CONVERT 函數(shù)使數(shù)據(jù)類(lèi)型相匹配。另一個(gè)示例:雖然 T-SQL 會(huì)在進(jìn)行比較之前對(duì)字符表達(dá)式進(jìn)行隱式且自動(dòng)的 RTRIM,但不能依賴此行為,因?yàn)榧嫒菪约?jí)別設(shè)置非字符表達(dá)式會(huì)使情況復(fù)雜化。
不要將空的變量值直接與比較運(yùn)算符(符號(hào))比較。如果變量可能為空,應(yīng)使用 IS NULL 或 IS NOT NULL 進(jìn)行比較,或者使用 ISNULL 函數(shù)。
不要使用 STR 函數(shù)進(jìn)行舍入,此函數(shù)只能用于整數(shù)。如果需要十進(jìn)制值的字符串形式,應(yīng)先使用 CONVERT 函數(shù)(轉(zhuǎn)至不同的范圍)或 ROUND 函數(shù),然后將其轉(zhuǎn)換為字符串。也可以使用 CEILING 和 FLOOR 函數(shù)。
使用數(shù)學(xué)公式時(shí)要小心,因?yàn)?T-SQL 可能會(huì)將表達(dá)式強(qiáng)制理解為一個(gè)不需要的數(shù)據(jù)類(lèi)型。如果需要十進(jìn)制結(jié)果,應(yīng)在整數(shù)常量后加點(diǎn)和零 (.0)。
決不要依賴 SELECT 語(yǔ)句會(huì)按任何特定順序返回行,除非在 ORDER BY 子句中指定了順序。
通常,應(yīng)將 ORDER BY 子句與 SELECT 語(yǔ)句一起使用??深A(yù)知的順序(即使不是最方便的)比不可預(yù)知的順序強(qiáng),尤其是在開(kāi)發(fā)或調(diào)試過(guò)程中。(部署到生產(chǎn)環(huán)境中之前,可能需要?jiǎng)h除 ORDER BY 子句。)在返回行的順序無(wú)關(guān)緊要的情況下,可以忽略 ORDER BY 的開(kāi)銷(xiāo)。
不要在 T-SQL 代碼中使用雙引號(hào)。應(yīng)為字符常量使用單引號(hào)。如果沒(méi)有必要限定對(duì)象名稱(chēng),可以使用(非 ANSI SQL 標(biāo)準(zhǔn))括號(hào)將名稱(chēng)括起來(lái)。
在 SQL Server 2000 中,盡量使用表變量來(lái)代替臨時(shí)表。如果表變量包含大量數(shù)據(jù),請(qǐng)注意索引非常有限(只有主鍵索引)。
先在例程中創(chuàng)建臨時(shí)表,最后再顯式刪除臨時(shí)表。將 DDL 與 DML 語(yǔ)句混合使用有助于處理額外的重新編譯活動(dòng)。
要認(rèn)識(shí)到臨時(shí)表并不是不可使用,適當(dāng)?shù)厥褂盟鼈兛梢允鼓承├谈行В?,?dāng)需要重復(fù)引用大型表或常用表中的某個(gè)數(shù)據(jù)集時(shí)。但是,對(duì)于一次性事件,最好使用導(dǎo)出表。
使用表值 UDF 時(shí)要小心,因?yàn)樵谧兞浚ǘ皇浅A浚┲袀鬟f某個(gè)參數(shù)時(shí),如果在 WHERE 子句中使用該參數(shù),會(huì)導(dǎo)致表掃描。還要避免在一個(gè)查詢中多次使用相同的表值 UDF。但是,表值 UDF 確實(shí)具有某些非常方便的動(dòng)態(tài)編譯功能。[相關(guān)資料:參閱 Tom Moreau 在 2003 年 11 月份“生成序列號(hào)”專(zhuān)欄中的“使用 UDF 填充表變量”。-編者按]
幾乎所有的存儲(chǔ)過(guò)程都應(yīng)在開(kāi)始時(shí)設(shè)置 SET NOCOUNT ON,而在結(jié)束時(shí)設(shè)置 SET NOCOUNT OFF。[SET NOCOUNT ON 使 SQL Server 無(wú)需在執(zhí)行存儲(chǔ)過(guò)程的每個(gè)語(yǔ)句后向客戶端發(fā)送 DONE_IN_PROC 消息。- 編者按] 此標(biāo)準(zhǔn)同樣適用于觸發(fā)器。
只要在例程中使用多個(gè)數(shù)據(jù)庫(kù)修改語(yǔ)句,包括在一個(gè)循環(huán)中多次執(zhí)行一個(gè)語(yǔ)句,就應(yīng)考慮聲明顯式事務(wù)。
使用基于光標(biāo)的方法或臨時(shí)表方法之前,應(yīng)先尋找基于集的解決方案來(lái)解決問(wèn)題?;诩姆椒ㄍǔ8行?。
與臨時(shí)表一樣,光標(biāo)并不是不可使用。對(duì)小型數(shù)據(jù)集使用 FAST_FORWARD 光標(biāo)通常要優(yōu)于其他逐行處理方法,尤其是在必須引用幾個(gè)表才能獲得所需的數(shù)據(jù)時(shí)。在結(jié)果集中包括“合計(jì)”的例程通常要比使用光標(biāo)執(zhí)行的速度快。如果開(kāi)發(fā)時(shí)間允許,基于光標(biāo)的方法和基于集的方法都可以嘗試一下,看哪一種方法的效果更好。
使用包含序號(hào)(從 1 到 N)的表很方便。
理解 CROSS JOIN 的工作原理并加以利用。例如,您可以在工作數(shù)據(jù)表和序號(hào)表之間有效地使用 CROSS JOIN,結(jié)果集中將包含每個(gè)工作數(shù)據(jù)與序號(hào)組合的記錄。
我的結(jié)束語(yǔ)是:T-SQL 代碼往往很簡(jiǎn)潔,因此如果某個(gè)代碼塊看起來(lái)很難處理或重復(fù)內(nèi)容較多,那么可能存在一種更簡(jiǎn)單,更好的方法。
結(jié)論
如果您對(duì)我的建議有任何看法,歡迎隨時(shí)向我發(fā)送電子郵件進(jìn)行討論,也可以就其他問(wèn)題提出您的建議。我希望您將此作為談話的開(kāi)場(chǎng)白。
其他信息:摘自 Karen 2000 年 2 月份的社論
在標(biāo)準(zhǔn)開(kāi)發(fā)的前沿陣地上,有一股以 SQL Server 數(shù)據(jù)庫(kù)管理員 John Hindmarsh 為首的獨(dú)立的新生力量。MCT、MCSE 和 MCDBA 都是最值得您花時(shí)間去研究的。John 的貢獻(xiàn)是撰寫(xiě)了一份詳細(xì)的白皮書(shū),概述了他對(duì)各種 SQL Server 相關(guān)標(biāo)準(zhǔn)提出的建議。我所知道的其他唯一提出類(lèi)似建議的文章是 Andrew Zanevsky 的《Transact-SQL Programming》(ISBN 1-56592-401-0) 中的“Format and Style”一章。Andrew、SQL Server Professional 的投稿人 Tom Moreau 和 Paul Munkenbeck 以及 John 的朋友兼同事 Stephen James 都為 John 的白皮書(shū)做出過(guò)貢獻(xiàn)。下面是 John 為編寫(xiě)存儲(chǔ)過(guò)程提供的建議示例:
• | 使用 SQL-92 標(biāo)準(zhǔn)連接句法。 |
• | 為了提高性能,應(yīng)優(yōu)先使用連接,然后使用子查詢或嵌套查詢。 |
• | 確保變量和參數(shù)的類(lèi)型和大小與表數(shù)據(jù)列相匹配。 |
• | 確保使用所有變量和參數(shù),或者全部刪除。 |
• | 盡可能將臨時(shí)對(duì)象放置在本地。 |
• | 只使用在存儲(chǔ)過(guò)程中創(chuàng)建的臨時(shí)表。 |
• | 檢查輸入?yún)?shù)的有效性。 |
• | 優(yōu)先使用 SELECT...INTO,然后使用 INSERT...SELECT,以避免大量死鎖。 |
• | 維護(hù)工作需要的邏輯單元;在可以縮短的情況下,不要?jiǎng)?chuàng)建大量或長(zhǎng)時(shí)間運(yùn)行的進(jìn)程。 |
• | 不要在任何代碼中使用 SELECT *。 |
• | 在過(guò)程中使用縮進(jìn)、塊、制表符和空格(參閱示例腳本)。 |
• | T-SQL 語(yǔ)句要大寫(xiě)。 |
• | 在過(guò)程中添加大量注釋?zhuān)_??梢宰R(shí)別進(jìn)程。在有助于澄清處理步驟的地方使用行注釋。 |
• | 包括事務(wù)管理,除非要從 MTS 進(jìn)程中調(diào)用過(guò)程。(為 MTS 進(jìn)程編寫(xiě)?yīng)毩⒌倪^(guò)程。) |
• | 監(jiān)視 @@TRANCOUNT 以確定事務(wù)的責(zé)任級(jí)別。 |
• | 避免使用 GOTO,錯(cuò)誤處理程序中除外。 |
• | 避免使用嵌套過(guò)程。 |
• | 避免隱式解析對(duì)象名稱(chēng),確保所有對(duì)象都?xì)w dbo 所有。 |
聯(lián)系客服