SQL 语句推荐标准
可能让人觉得很奇怪,但好像的确没有什么“正式的”T-SQL 编码标准。早在 1999 年末的时候,我惊喜地发现 John Hindmarsh 提出的 SQL Server 7.0 标准,我在 2000 年 2 月的社论中对他的某些建议进行了总结。(2000 年 2 月以及本月的“下载”中都包括了 John 原来的标准。)后来,Ron Talmage 撰写了一系列专栏文章,提出了他对各种“最佳方法”的建议,当然,SQL Server 小组也已正式发布了 SQL Server 最佳方法分析器 (SQLBPA)。现在,一位具有超过 25 年经验的数据库管理员和应用程序开发员 Brian Walker 又提出了他的建议和提示。
进行 T-SQL 编程时常常会忽略编码标准,但这些标准却是开发小组顺利开展工作的关键工具。这里介绍的编码标准是我多年的开发成果。它们当然还没有得到普遍接受,而且不可否认,有些标准带有主观色彩。我的目的实际上更多的是为了提高大家的意识,而不是吹捧自己是 T-SQL 样式方面的仲裁者:最重要的是要建立某些合理的编码标准并遵循这些标准。您在这篇文章中会发现有关 T-SQL 编程的一系列不同的编码标准、技巧和提示。它们并未以任何特定的优先级或重要性顺序列出。
让我们从格式开始。表面上,T-SQL 代码的格式似乎并不重要,但一致的格式可以使您的同事(不论是同一小组的成员还是更大范围的 T-SQL 开发团队的成员)更轻松地浏览和理解您的代码。T-SQL 语句有一个结构,遵循一目了然的结构使您可以更轻松地查找和确认语句的不同部分。统一的格式还使您可以更轻松地在复杂 T-SQL 语句中增删代码段,使调试工作变得更容易。下面是 SELECT 语句的格式示例:
SELECT C.Name , E.NameLast , E.NameFirst , E.Number , ISNULL(I.Description,'NA') AS Description FROM tblCompany AS C JOIN tblEmployee AS E ON C.CompanyID = E.CompanyID LEFT JOIN tblCoverage AS V ON E.EmployeeID = V.EmployeeID LEFT JOIN tblInsurance AS I ON V.InsuranceID = I.InsuranceID WHERE C.Name LIKE @Name AND 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 = @@ROWCOUNT IF @Status = 0 SET @Status = @Retain
►一个嵌套代码块中的语句使用四个空格的缩进。(上述代码中的多行 SELECT 语句是一个 SQL 语句。)在同一语句中开始新行时,使 SQL 关键字右对齐。将代码编辑器配置为使用空格,而不是使用制表符。这样,不管使用何种程序查看代码,格式都是一致的。
►大写所有的 T-SQL 关键字,包括 T-SQL 函数。变量名称及光标名称使用混和大小写。数据类型使用小写。
►表名别名要简短,但意义要尽量明确。通常,使用大写的表名作为别名,使用 AS 关键字指定表或字段的别名。
►当一个 T-SQL 语句中涉及到多个表时,始终使用表名别名来限定字段名。这使其他人阅读起来更清楚,避免了含义模糊的引用。
►当相关数字出现在连续的代码行中时(例如一系列 SUBSTRING 函数调用),将它们排成列。这样容易浏览数字列表。
►使用一个(而不是两个)空行分隔 T-SQL 代码的逻辑块,只要需要就可以使用。
►声明 T-SQL 局部变量(例如 @lngTableID)时,使用适当的数据类型声明和一致的大写。
►始终指定字符数据类型的长度,并确保允许用户可能需要的最大字符数,因为超出最大长度的字符会丢失。
►始终指定十进制数据类型的精度和范围,否则,将默认为未指定精度和整数范围。
► 使用错误处理程序,但要记住行首 (BOL) 中的错误检查示例不会象介绍的那样起作用。用来检查 @@ERROR 系统函数的 T-SQL 语句 (IF) 实际上在进程中清除了 @@ERROR 值,无法再捕获除零之外的任何值。(即使示例起作用,它们也只能捕获最后发生的一个错误,而不是您更想捕获的第一个错误。)必须使用 SET 或 SELECT 立即捕获错误代码,如前面示例所示。如果状态变量仍然为零,应转换到状态变量。
►避免使用“未声明的”功能,例如系统表中未声明的列、T-SQL 语句中未声明的功能或者未声明的系统存储过程或扩展的存储过程。
►不要依赖任何隐式的数据类型转换。例如,不能为数字变量赋予字符值,而假定 T-SQL 会进行必要的转换。相反,在为变量赋值或比较值之前,应使用适当的 CONVERT 函数使数据类型相匹配。另一个示例:虽然 T-SQL 会在进行比较之前对字符表达式进行隐式且自动的 RTRIM,但不能依赖此行为,因为兼容性级别设置非字符表达式会使情况复杂化。
►不要将空的变量值直接与比较运算符(符号)比较。如果变量可能为空,应使用 IS NULL 或 IS NOT NULL 进行比较,或者使用 ISNULL 函数。
►不要使用 STR 函数进行舍入,此函数只能用于整数。如果需要十进制值的字符串形式,应先使用 CONVERT 函数(转至不同的范围)或 ROUND 函数,然后将其转换为字符串。也可以使用 CEILING 和 FLOOR 函数。
►使用数学公式时要小心,因为 T-SQL 可能会将表达式强制理解为一个不需要的数据类型。如果需要十进制结果,应在整数常量后加点和零 (.0)。
►决不要依赖 SELECT 语句会按任何特定顺序返回行,除非在 ORDER BY 子句中指定了顺序。
► 通常,应将 ORDER BY 子句与 SELECT 语句一起使用。可预知的顺序(即使不是最方便的)比不可预知的顺序强,尤其是在开发或调试过程中。(部署到生产环境中之前,可能需要删除 ORDER BY 子句。)在返回行的顺序无关紧要的情况下,可以忽略 ORDER BY 的开销。
►不要在 T-SQL 代码中使用双引号。应为字符常量使用单引号。如果没有必要限定对象名称,可以使用(非 ANSI SQL 标准)括号将名称括起来。
►在 SQL Server 2000 中,尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。
►先在例程中创建临时表,最后再显式删除临时表。将 DDL 与 DML 语句混合使用有助于处理额外的重新编译活动。
►要认识到临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。
►使用表值 UDF 时要小心,因为在变量(而不是常量)中传递某个参数时,如果在 WHERE 子句中使用该参数,会导致表扫描。还要避免在一个查询中多次使用相同的表值 UDF。但是,表值 UDF 确实具有某些非常方便的动态编译功能。[相关资料:参阅 Tom Moreau 在 2003 年 11 月份“生成序列号”专栏中的“使用 UDF 填充表变量”。-编者按]
►几乎所有的存储过程都应在开始时设置 SET NOCOUNT ON,而在结束时设置 SET NOCOUNT OFF。[SET NOCOUNT ON 使 SQL Server 无需在执行存储过程的每个语句后向客户端发送 DONE_IN_PROC 消息。- 编者按] 此标准同样适用于触发器。
►只要在例程中使用多个数据库修改语句,包括在一个循环中多次执行一个语句,就应考虑声明显式事务。
►使用基于光标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题。基于集的方法通常更有效。
► 与临时表一样,光标并不是不可使用。对小型数据集使用 FAST_FORWARD 光标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用光标执行的速度快。如果开发时间允许,基于光标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。
►使用包含序号(从 1 到 N)的表很方便。
►理解 CROSS JOIN 的工作原理并加以利用。例如,您可以在工作数据表和序号表之间有效地使用 CROSS JOIN,结果集中将包含每个工作数据与序号组合的记录。
►我的结束语是:T-SQL 代码往往很简洁,因此如果某个代码块看起来很难处理或重复内容较多,那么可能存在一种更简单,更好的方法。
结论
如果您对我的建议有任何看法,欢迎随时向我发送电子邮件进行讨论,也可以就其他问题提出您的建议。我希望您将此作为谈话的开场白。
其他信息:摘自 Karen 2000 年 2 月份的社论
在标准开发的前沿阵地上,有一股以 SQL Server 数据库管理员 John Hindmarsh 为首的独立的新生力量。MCT、MCSE 和 MCDBA 都是最值得您花时间去研究的。John 的贡献是撰写了一份详细的白皮书,概述了他对各种 SQL Server 相关标准提出的建议。我所知道的其他唯一提出类似建议的文章是 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 的白皮书做出过贡献。下面是 John 为编写存储过程提供的建议示例:
• |
使用 SQL-92 标准连接句法。 |
• |
为了提高性能,应优先使用连接,然后使用子查询或嵌套查询。 |
• |
确保变量和参数的类型和大小与表数据列相匹配。 |
• |
确保使用所有变量和参数,或者全部删除。 |
• |
尽可能将临时对象放置在本地。 |
• |
只使用在存储过程中创建的临时表。 |
• |
检查输入参数的有效性。 |
• |
优先使用 SELECT…INTO,然后使用 INSERT…SELECT,以避免大量死锁。 |
• |
维护工作需要的逻辑单元;在可以缩短的情况下,不要创建大量或长时间运行的进程。 |
• |
不要在任何代码中使用 SELECT *。 |
• |
在过程中使用缩进、块、制表符和空格(参阅示例脚本)。 |
• |
T-SQL 语句要大写。 |
• |
在过程中添加大量注释,确保可以识别进程。在有助于澄清处理步骤的地方使用行注释。 |
• |
包括事务管理,除非要从 MTS 进程中调用过程。(为 MTS 进程编写独立的过程。) |
• |
监视 @@TRANCOUNT 以确定事务的责任级别。 |
• |
避免使用 GOTO,错误处理程序中除外。 |
• |
避免使用嵌套过程。 |
• |
避免隐式解析对象名称,确保所有对象都归 dbo 所有。 |