我什么时候不应该使用分号?

时间:2011-11-04 02:39:01

标签: tsql

或者:什么不是T-SQL语句?

除了解决歧义之外,T-SQL语法不需要分号来终止语句。尽管如此,Itzik Ben-Gan建议使用分号来终止T-SQL语句,因为它使代码更清晰,更易读,更易于维护,更便于移植。

我不知道有效T-SQL语句的确切定义,所以我可能会在这里感到困惑。但据我所知,BEGIN ... END块是一个T-SQL语句,因此应以分号结束。例如:

IF OBJECT_ID('tempdb.dbo.#TempTable') IS NOT NULL
BEGIN
  DROP TABLE #TempTable;
END;

Microsoft的BEGIN...END documentation中的代码示例支持这个猜想:

USE AdventureWorks2008R2;
GO
BEGIN TRANSACTION;
GO
IF @@TRANCOUNT = 0
BEGIN
    SELECT FirstName, MiddleName 
    FROM Person.Person WHERE LastName = 'Adams';
    ROLLBACK TRANSACTION;
    PRINT N'Rolling back the transaction two times would cause an error.';
END;
ROLLBACK TRANSACTION;
PRINT N'Rolled back the transaction.';
GO
/*
Rolled back the tranaction.
*/

Itzik Ben-Gan在T-SQL Fundamentals的练习1-1的代码示例中与此相矛盾:

SET NOCOUNT ON;
USE TSQLFundamentals2008;
IF OBJECT_ID('dbo.Nums', 'U') IS NOT NULL DROP TABLE dbo.Nums;
CREATE TABLE dbo.Nums(n INT NOT NULL PRIMARY KEY);

DECLARE @i AS INT = 1;
BEGIN TRAN
  WHILE @i <= 100000
  BEGIN
    INSERT INTO dbo.Nums VALUES(@i);
    SET @i = @i + 1;
  END
COMMIT TRAN
SET NOCOUNT OFF;

Microsoft的Transact-SQL Syntax Conventions文档声明在T-SQL的未来版本中将需要分号。

在评论微软打算在未来版本的T-SQL中要求分号时,Itzik指出了一些不应该被终止的例外:

  

到目前为止,仅在特定情况下才需要使用分号。现在看起来计划是使它成为SQL Server未来版本中所有* T-SQL语句的必需终结符。

     

(*)当然有些情况不应以分号结尾;这些包括(但不限于):

     
      
  • BEGIN

  •   
  • BEGIN TRAN

  •   
  • 如果

  •   
  • ELSE

  •   
  • WHILE

  •   
  • 开始尝试

  •   
  • 结束尝试

  •   
  • BEGIN CATCH

  •   

Itzik似乎与自己一致,但微软本身并不遵循他的建议。在前面的示例中比较Microsoft的BEGIN TRANSACTION;和Itzik的BEGIN TRAN

在我维护的代码中,我甚至看到BEGIN关键字以分号结尾:

IF @HasWidget = 0x1
BEGIN;
  SELECT WidgetID
  FROM tbWidgets;
END;

我相信T-SQL解析器可能会考虑BEGIN关键字后面的分号来终止空语句,而不是终止BEGIN关键字本身;我不相信BEGIN本身就是一个有效的T-SQL语句。

SQL Server 2008成功解析并执行以下查询这一事实支持了这一猜想:

SELECT 0;;

这很混乱,因为没有广泛可用的T-SQL语言规范,比如Java的Java Language Specification,所以没有T-SQL语句的正式定义。

我错了吗? T-SQL是否存在这样的规范,是否公开发布?

否则,我应该相信Itzik所说的吗?

3 个答案:

答案 0 :(得分:25)

  

T-SQL语法不需要分号来终止语句。

实际上,这是deprecated 1 。我不记得了,但我认为你仍然可以在即将推出的Sql Server 2012中使用它们,但是之后的某个版本可能需要为每个语句使用分号。 ansi standard在技术上也需要使用分号。关键是现在是时候养成为每个陈述使用一个的习惯。

实际上,我不希望他们直接遵循这一点。相反,我希望Sql Server Management Studio和其他开发工具首先开始发出警告而不是错误,可能是针对多个版本。这将有助于开发人员查找并修复所有旧的不合规代码。但这并没有减少这一信息:半冒险即将来临。

对于使用分号时的简单启发式算法,请将代码视为使用大括号的过程语言,如C / C ++。如果用过程语言编写,将与开头(非结束)大括号配对的语句不应该是分号。

1 它几乎一直在页面底部

答案 1 :(得分:14)

摘要,基于OP的原始引用列表。

是分号:

  • BEGIN TRAN;

没有分号:

  • BEGIN
  • IF
  • ELSE
  • WHILE
  • 开始尝试
  • END TRY
  • BEGIN CATCH

另外,请在ENDEND CATCH之后使用它们。

详细说明:

BEGIN TRAN是一个声明,应以分号结尾。

Microsoft的文档说明了可选的分号:

BEGIN { TRAN | TRANSACTION } 
    [ { transaction_name | @tran_name_variable }
      [ WITH MARK [ 'description' ] ]
    ]
[ ; ]

微软的例子有分号:

BEGIN TRAN T1;
UPDATE table1 ...;
BEGIN TRAN M2 WITH MARK;
UPDATE table2 ...;
SELECT * from table1;
COMMIT TRAN M2;
UPDATE table3 ...;
COMMIT TRAN T1;

以上两点均来自:

https://msdn.microsoft.com/en-us/library/ms188929(v=sql.90).aspx

它们符合当前文档:

https://msdn.microsoft.com/en-us/library/ms188929(v=sql.120).aspx

对于BEGIN...END,Microsoft文档未提供明确的指导。

该定义没有分号:

BEGIN
     { 
    sql_statement | statement_block 
     } 
END

但是,他们的示例在END之后显示了一个分号:

IF @@TRANCOUNT = 0
BEGIN
    SELECT FirstName, MiddleName 
    FROM Person.Person WHERE LastName = 'Adams';
    ROLLBACK TRANSACTION;
    PRINT N'Rolling back the transaction two times would cause an error.';
END;

https://msdn.microsoft.com/en-us/library/ms190487.aspx

这个尾随的分号与微软自己的IF控制流语言结构的文档不一致:

IF Boolean_expression 
     { sql_statement | statement_block } 
[ ELSE 
     { sql_statement | statement_block } ] 

该定义及其代码示例均未显示任何分号:

DECLARE @compareprice money, @cost money 
EXECUTE Production.uspGetList '%Bikes%', 700, 
    @compareprice OUT, 
    @cost OUTPUT
IF @cost <= @compareprice 
BEGIN
    PRINT 'These products can be purchased for less than 
    $'+RTRIM(CAST(@compareprice AS varchar(20)))+'.'
END
ELSE
    PRINT 'The prices for all products in this category exceed 
    $'+ RTRIM(CAST(@compareprice AS varchar(20)))+'.'

https://msdn.microsoft.com/en-us/library/ms182717(v=sql.110).aspx

但是,他们的ELSE文档虽然在定义中没有显示任何分号,但在最后END之后,确实会在示例中显示一个。

定义:

IF Boolean_expression { sql_statement | statement_block } 
    [ ELSE { sql_statement | statement_block } ] 

示例:

IF 1 = 1 PRINT 'Boolean_expression is true.'
ELSE PRINT 'Boolean_expression is false.' ;

https://msdn.microsoft.com/en-us/library/ms182587(v=sql.110).aspx

ANSI标准不解决歧义,因为它们是非标准扩展:

  

ANSI SQL标准不涵盖控制流语句   因为这些是专有的SQL扩展。 SQL Server书籍   在线是关于这个主题和许多例子的粗略概述(截至此   写作)是不一致的,并不总是包含声明   终止。此外,控制流语句块是   由于许多变化,嵌套和可选的BEGIN / END而导致混乱   规格。

http://www.dbdelta.com/always-use-semicolon-statement-terminators/

然而,服务器的行为有所启发。以下不是SQL Server 2005中的语法错误:

DECLARE @foo int;
IF @foo IS NULL
BEGIN
    WITH Blah AS
    (
        SELECT
            'a' AS a
    )
    SELECT
        a
    FROM        Blah;
END

因此BEGIN本身不需要分号。但是,以下确实在SQL Server 2005中产生语法错误:

DECLARE @foo int;
IF @foo IS NULL
BEGIN
    WITH Blah AS
    (
        SELECT
            'a' AS a
    )
    SELECT
        a
    FROM        Blah;
END
WITH Blah2 AS
(
    SELECT
        'a' AS a
)
SELECT
    a
FROM        Blah2;

以上导致此错误:

  

消息319,级别15,状态1,行13关键字附近的语法不正确   “用”。如果此语句是公用表表达式或   xmlnamespaces子句,前一个语句必须以a结尾   分号。

它还会在SQL Server 2008 R2中抛出该错误。

它变得更加令人困惑。 Microsoft TRY...CATCH的文档在END CATCH之后显示了一个可选的分号,并且它们的示例与此一致。

BEGIN TRY
     { sql_statement | statement_block }
END TRY
BEGIN CATCH
     [ { sql_statement | statement_block } ]
END CATCH
[ ; ]

但是,如果您在BEGIN TRY之后立即进行CTE而没有分号,则会产生错误。

BEGIN TRY
    WITH Blah AS
    (
        SELECT
            'a' AS a
    )
    SELECT
        a
    FROM        Blah;
END TRY
BEGIN CATCH
END CATCH

在SQL Server 2008 R2中,上述批处理会抛出此错误:

  

消息319,级别15,状态1,行2关键字附近的语法不正确   “用”。如果此语句是公用表表达式,则为   xmlnamespaces子句或更改跟踪上下文子句,前一个   声明必须以分号结束。

错误意味着BEGIN TRY是一个语句(它不是),并且分号“修复”了问题(它确实如此)。没错,这很有效:

BEGIN TRY;
    WITH Blah AS
    (
        SELECT
            'a' AS a
    )
    SELECT
        a
    FROM        Blah;
END TRY
BEGIN CATCH
END CATCH

然而,微软表示这不是一个好的做法:

  

微软发表于2009年12月29日下午12:11我正在解决问题   将SQL11错误视为“按设计”。以下是解释:

     

不应允许END TRY和BEGIN CATCH之间的分号,   因为它们实际上并不是不同的陈述,而是部分的   同样的TRY-CATCH声明。我们只在分开时才允许使用分号   序列中的两个陈述。

     

解释为什么我们在BEGIN TRY和之后允许使用分号   开始捕捉。这些关键字用作开头的“括号”   嵌入语句序列。 BEGIN TRY / BEGIN CATCH后的分号   使用第一个语句将其解析为嵌入序列的一部分   在序列中为空。虽然我们允许这种语法,但我不会   推荐它作为一种良好的编码实践,因为它会造成错误   BEGIN TRY / BEGIN CATCH独立,独立的印象   语句。

处理这种情况的推荐方法是为了清晰起见而增加BEGIN...END

BEGIN TRY
    BEGIN
        WITH Blah AS
        (
            SELECT
                'a' AS a
        )
        SELECT
            a
        FROM        Blah;
    END
END TRY
BEGIN CATCH
END CATCH

但是,END之前的END TRY可能应该有一个分号。毕竟,这会引发错误:

BEGIN TRY
    BEGIN
        WITH Blah AS
        (
            SELECT
                'a' AS a
        )
        SELECT
            a
        FROM        Blah;
    END
    WITH Blah2 AS
    (
        SELECT
            'b' AS b
    )
    SELECT
        b
    FROM        Blah2;
END TRY
BEGIN CATCH
END CATCH

也许总是在CTE WITH之前,分号并不是那么愚蠢。

答案 2 :(得分:5)

我经常使用分号的唯一情况是通过WITH关键字使用Common Table Expressions - 然后才会因为WITH关键字前面必须加分号,否则返回一个错误。在那些情况下,我写了

;WITH [exp]...

即。我在WITH之前加上分号,而不是终止前一个语句。

SQL中的分号用法似乎非常少见;我偶尔会在存储过程或函数声明之后看到它,这是异常而不是规则。在我与之合作的所有开发人员中,我不相信任何人真正以你所描述的方式使用分号。

之类的陈述
BEGIN;
    SELECT WidgetID
    FROM tbWidgets;    
END;  

很难理解 - 如果BEGIN;被视为独立于其对应END;的声明,为什么SELECT WidgetID不是独立于其对应FROM的有效声明?< / p>