Skip to content

Commit 20e7201

Browse files
daiplusplusrwestMSFT
authored andcommitted
Improving to BEGIN...END in T-SQL
1 parent dda1c02 commit 20e7201

1 file changed

Lines changed: 58 additions & 25 deletions

File tree

docs/t-sql/language-elements/begin-end-transact-sql.md

Lines changed: 58 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,25 @@ monikerRange: ">=aps-pdw-2016 || =azuresqldb-current || =azure-sqldw-latest || >
2828

2929
[!INCLUDE [sql-asdb-asdbmi-asa-pdw-fabricse-fabricdw-fabricsqldb](../../includes/applies-to-version/sql-asdb-asdbmi-asa-pdw-fabricse-fabricdw-fabricsqldb.md)]
3030

31-
Encloses a series of [!INCLUDE [tsql](../../includes/tsql-md.md)] statements so that a group of [!INCLUDE [tsql](../../includes/tsql-md.md)] statements can be executed in a logical block of code. `BEGIN` and `END` are control-of-flow language keywords.
31+
Encloses a sequence of [!INCLUDE [tsql](../../includes/tsql-md.md)] statements into a logical block of code. Note this use of "`BEGIN`" is unrelated to the `BEGIN TRANSACTION` and `BEGIN ATOMIC` statements.
3232

33-
:::image type="icon" source="../../includes/media/topic-link-icon.svg" border="false"::: [Transact-SQL syntax conventions](../../t-sql/language-elements/transact-sql-syntax-conventions-transact-sql.md)
33+
`BEGIN...END` blocks are often used with a preceding flow-control statement such as `IF`, `ELSE` and `WHILE`, but these blocks can also be used without any preceding flow-control to aesthetically group sequences of statements in a way similar to an anonymous scope `{ ... }` in C-style languages except that `BEGIN...END` blocks do not create a new lexical scope.
34+
35+
:::image type="icon" source="../../includes/media/topic-link-icon.svg" border="false"::: [Transact-SQL syntax conventions](../../t-sql/language-elements/transact-sql-syntax-conventions-transact-sql.md#:~:text=semicolon)
3436

3537
## Syntax
3638

3739
```syntaxsql
38-
BEGIN
40+
BEGIN[;]
3941
{ sql_statement | statement_block }
40-
END
42+
END[;]
4143
```
4244

45+
* The use of semicolons after the `BEGIN` and `END` keywords is optional [but recommended]((../../t-sql/language-elements/transact-sql-syntax-conventions-transact-sql.md)), excepting for some cases where they are required, such as when a CTE (`WITH`) or `THROW` statement is used within a block.
46+
* Using a semicolon after `BEGIN` can help avoid potential confusion with the `BEGIN TRANSACTION` or `BEGIN ATOMIC` statements.
47+
Using a semicolon after `END` ensures that any subsequent statement, in particular the `WITH` keyword or `THROW` statement, will not need any preceding semicolon.
48+
* `BEGIN...END` must contain at least one statement: attempting to use an empty `BEGIN...END` block will result in a syntax error, even when each keyword is used with a semicolon terminator.
49+
4350
## Arguments
4451

4552
#### { *sql_statement* | *statement_block* }
@@ -48,51 +55,77 @@ Any valid [!INCLUDE [tsql](../../includes/tsql-md.md)] statement or statement gr
4855

4956
## Remarks
5057

51-
`BEGIN...END` blocks can be nested.
58+
* `BEGIN...END` blocks can be nested.
59+
* `BEGIN...END` blocks cannot span multiple batches, i.e. the `GO` batch separator cannot be used inside a `BEGIN...END` block.
60+
* `BEGIN...END` blocks do not define any lexical scope: a variable declared within a block will be visible throughout the parent batch and not just within the block containing the `DECLARE` statement.
61+
* Using a `BEGIN...END` block to group statements does not imply all that all statements in the group will be executed atomically: When a batch runs outside of a transaction and an error is raised or an exception is thrown by the 2nd statement of a multi-statement `BEGIN...END` block then the 1st statement will not be rolled-back.
62+
* To avoid having a (syntactically invalid) empty `BEGIN...END` block, you may use a `GOTO` label as a "no-op" placeholder statement.
5263

53-
Although all [!INCLUDE [tsql](../../includes/tsql-md.md)] statements are valid within a `BEGIN...END` block, certain [!INCLUDE [tsql](../../includes/tsql-md.md)] statements shouldn't be grouped together within the same batch, or statement block.
64+
Although all [!INCLUDE [tsql](../../includes/tsql-md.md)] statements are valid within a `BEGIN...END` block, certain [!INCLUDE [tsql](../../includes/tsql-md.md)] statements shouldn't be grouped together within the same batch, or statement block<!-- TODO: Is there an authoritative list of statements that should not be used? -->.
5465

5566
## Examples
5667

57-
In the following example, `BEGIN` and `END` define a series of [!INCLUDE [tsql](../../includes/tsql-md.md)] statements that execute together. If the `BEGIN...END` block isn't included, both `ROLLBACK TRANSACTION` statements would execute, and both `PRINT` messages would be returned.
68+
In the following example, `BEGIN` and `END` define sequences of logically related [!INCLUDE [tsql](../../includes/tsql-md.md)] statements to be executed in-order; nested blocks are also demonstrated.
5869

5970
```sql
6071
USE AdventureWorks2022;
61-
GO
6272

63-
BEGIN TRANSACTION
6473
GO
6574

66-
IF @@TRANCOUNT = 0
67-
BEGIN
68-
SELECT FirstName, MiddleName
69-
FROM Person.Person
70-
WHERE LastName = 'Adams';
75+
DECLARE @personId int = ( SELECT p.BusinessEntityID FROM Person.Person AS p WHERE p.rowguid = {guid'92C4279F-1207-48A3-8448-4636514EB7E2'} );
76+
IF( @personId IS NULL ) THROW 50001, 'Person not found.', 1;
77+
78+
/* Concatenate the person's name fields: */
79+
BEGIN;
80+
DECLARE @title nvarchar(8), @first nvarchar(50), @middle nvarchar(50), @last nvarchar(50), @suffix nvarchar(10);
81+
82+
SELECT
83+
@title = NULLIF( p.Title, N'' ),
84+
@first = p.FirstName,
85+
@middle = NULLIF( p.MiddleName, N'' ),
86+
@last = p.LastName,
87+
@suffix = NULLIF( p.Suffix, N'' )
88+
FROM
89+
Person.Person AS p
90+
WHERE
91+
p.BusinessEntityID = @personId;
92+
93+
DECLARE @nameConcat nvarchar(255) = CONCAT_WS( /*separator: */ N' ', @title, @first, @middle, @last, @suffix );
94+
95+
/* This is a nested BEGIN...END block: */
96+
BEGIN;
97+
DECLARE @emails nvarchar(max) = ( SELECT STRING_AGG( e.EmailAddress, /*separator:*/ N'; ' ) FROM Person.EmailAddress AS e WHERE e.BusinessEntityID = @personId );
98+
SET @nameConcat = CONCAT( @nameConcat, N' (', @emails, N')' );
99+
END;
100+
101+
END;
71102

72-
ROLLBACK TRANSACTION;
103+
/* BEGIN...END blocks do not define a lexical scope, so even though @nameAndEmails is declared above, it is still in-scope after the END keyword. */
104+
SELECT @nameConcat AS NameAndEmails;
105+
```
73106

74-
PRINT N'Rolling back the transaction two times would cause an error.';
75-
END;
107+
## Empty blocks:
76108

77-
ROLLBACK TRANSACTION;
109+
If you are generating Dynamic SQL with a `BEGIN...END` block such that it's simpler for your program to always render the `BEGIN...END` keywords then you may use a `GOTO` label as a "NOOP" or placeholder statement:
78110

79-
PRINT N'Rolled back the transaction.';
80-
GO
111+
```sql
112+
BEGIN;
113+
unusedNoopLabel:
114+
END;
81115
```
82116

83117
## Examples: [!INCLUDE [ssazuresynapse-md](../../includes/ssazuresynapse-md.md)] and [!INCLUDE [ssPDW](../../includes/sspdw-md.md)]
84118

85-
In the following example, `BEGIN` and `END` define a series of [!INCLUDE [DWsql](../../includes/dwsql-md.md)] statements that run together. If the `BEGIN...END` block isn't included, the following example runs in a continuous loop.
119+
In the following example, `BEGIN` and `END` define a series of [!INCLUDE [DWsql](../../includes/dwsql-md.md)] statements that run together. If the `BEGIN` and `END` keywords are commented-out then the following example will run forever in an infinite loop because only the `SELECT` query will be looped by the `WHILE` statement while the `SET @Iteration += 1` statement will never be reached.
86120

87121
```sql
88-
-- Uses AdventureWorksDW
122+
USE AdventureWorksDW;
89123

90124
DECLARE @Iteration INT = 0;
91125

92126
WHILE @Iteration < 10
93-
BEGIN
94-
SELECT FirstName,
95-
MiddleName
127+
BEGIN;
128+
SELECT FirstName, MiddleName
96129
FROM dbo.DimCustomer
97130
WHERE LastName = 'Adams';
98131

0 commit comments

Comments
 (0)