When should I not use a semicolon?

前端 未结 3 1257
南旧
南旧 2020-12-28 13:11

Or: What is not a T-SQL statement?

Except to resolve ambiguity, T-SQL syntax does not require a semicolon to terminate a statement. Despite this, Itzik Ben-G

3条回答
  •  情话喂你
    2020-12-28 13:45

    Summary, based on the OP's original, quoted list.

    Yes semi-colon:

    • BEGIN TRAN;

    No semi-colon:

    • BEGIN
    • IF
    • ELSE
    • WHILE
    • BEGIN TRY
    • END TRY
    • BEGIN CATCH

    Also, use them after END and END CATCH.

    Details:

    BEGIN TRAN is a statement and should be terminated with a semi-colon.

    Microsoft's documentation notes the optional semi-colon:

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

    Microsoft's example has semi-colons:

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

    Both of the above are from:

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

    They match the current documentation:

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

    As for BEGIN...END, the Microsoft documentation does not provide clear guidance.

    The definition has no semi-colon:

    BEGIN
         { 
        sql_statement | statement_block 
         } 
    END
    

    However, their example shows a semi-colon after 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

    That trailing semi-colon is not consistent with Microsoft's own documentation for IF control of flow language construct:

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

    Neither that definition nor their code example shows any semi-colon:

    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

    However, their ELSE documentation, while also not showing any semi-colon in the definition, does show one in the example, after the final END.

    Definition:

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

    Example:

    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

    The ANSI standard doesn't resolve the ambiguity because these are non-standard extensions:

    Control-of-flow statements are not covered by the ANSI SQL standard because these are proprietary SQL extensions. The SQL Server Books Online is sketchy on the subject and many of the examples (as of this writing) are inconsistent and do not always include statement terminators. Furthermore, control-of-flow statement blocks are confusing due to the many variations, nesting, and optional BEGIN/END specifications.

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

    However, the behavior of the server sheds some light. The following is not a syntax error in SQL Server 2005:

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

    So the BEGIN itself does not require a semi-colon. However, the following does produce a syntax error in 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;
    

    The above results in this error:

    Msg 319, Level 15, State 1, Line 13 Incorrect syntax near the keyword 'with'. If this statement is a common table expression or an xmlnamespaces clause, the previous statement must be terminated with a semicolon.

    It also throws that error in SQL Server 2008 R2.

    It gets even more confusing. Microsoft's documentation for TRY...CATCH shows an optional semi-colon after the END CATCH, and their examples are consistent with that.

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

    However, if you have a CTE immediately after a BEGIN TRY, without a semi-colon, it will throw an error.

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

    In SQL Server 2008 R2, the above batch throws this error:

    Msg 319, Level 15, State 1, Line 2 Incorrect syntax near the keyword 'with'. If this statement is a common table expression, an xmlnamespaces clause or a change tracking context clause, the previous statement must be terminated with a semicolon.

    The error implies that BEGIN TRY is a statement (which it isn't), and that a semi-colon "fixes" the issue (which it does). That's right, this works:

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

    However, Microsoft says that's not good practice:

    Posted by Microsoft on 12/29/2009 at 12:11 PM I am resolving the corresonding SQL11 bug as "by design". Here is the explanation:

    The semicolon between END TRY and BEGIN CATCH should not be allowed, because they are actually not different statements, but parts of the same TRY-CATCH statement. We only allow semicolons when they separate two statements in a sequence.

    A word of explanation why then we allow semicolons after BEGIN TRY and BEGIN CATCH. These keywords serve as opening "parentheses" that start an embedded statement sequence. Semicolons after BEGIN TRY/BEGIN CATCH get parsed as part of that embedded sequence, with the first statement in the sequence being empty. While we allow this syntax, I would not recommend it as a good coding practice because it creates a wrong impression of BEGIN TRY/BEGIN CATCH being independent, standalone statements.

    The recommended way to handle that situation is with an extra BEGIN...END for clarity:

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

    However, that END before the END TRY should probably have a semi-colon. After all, this will throw an error:

    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
    

    Maybe always preceding a CTE WITH a semi-colon isn't so silly.

提交回复
热议问题