T-SQL GOTO Statement
Master T-SQL GOTO and labels — unconditional jumps, when GOTO is legitimate (centralized cleanup paths) vs when to use structured constructs instead, and the modern alternatives (TRY/CATCH, RETURN, WHILE).
GOTO transfers execution unconditionally to a label — a named point in the same batch, stored procedure, or trigger. Unlike IF or WHILE, GOTO doesn't check anything; when execution hits a GOTO, it jumps. Period.
IF, WHILE, BREAK, RETURN, and TRY ... CATCH handle nearly every flow you'd ever need. The two legitimate modern uses are centralized cleanup paths and the rare case of breaking out of nested loops.A label is an identifier followed by a colon. GOTO label jumps to it.
Rules
- The label and the GOTO must live in the same batch, procedure, or trigger.
- Labels are not case-sensitive in keywords but the name follows normal identifier rules.
- A
GOTOcan jump forward (skip ahead) or backward (loop). - You can't
GOTOinto the middle of an IF or WHILE block from outside, or into another procedure.
The classic illustration — though in real code you'd use WHILE instead:
DECLARE @add_number INT = 1;
Test_num:
IF @add_number <= 5
BEGIN
PRINT 'add_number value: ' + CAST(@add_number AS VARCHAR(10));
SET @add_number = @add_number + 1;
GOTO Test_num;
END;
WHILE @add_number <= 5
BEGIN
PRINT @add_number;
SET @add_number += 1;
END
Use GOTO for things WHILE can't do — not as a substitute for it.DECLARE @add_number INT = 10;
IF @add_number = 10
GOTO skip_section;
PRINT 'This line will be skipped because @add_number is 10';
skip_section:
PRINT 'This line will always execute';
One of the few patterns where GOTO genuinely helps: a procedure with multiple early-exit conditions that all need the same cleanup. Without GOTO you'd duplicate the cleanup; with GOTO it lives once.
DECLARE @input INT = -5,
@result INT;
-- Validation step 1
IF @input IS NULL
BEGIN
PRINT 'Error: input is NULL';
GOTO cleanup;
END
-- Validation step 2
IF @input < 0
BEGIN
PRINT 'Error: input is negative';
GOTO cleanup;
END
-- Main work (only reached if validations passed)
SET @result = @input * 2;
PRINT 'Result: ' + CAST(@result AS VARCHAR(10));
cleanup:
PRINT 'Cleanup done. Goodbye.';
TRY ... CATCH for error paths and RETURN for early exits — those handle the same use case more clearly. Reach for GOTO only when those don't fit.| Instead of GOTO for... | Use |
|---|---|
| Looping | WHILE |
| Exiting a loop early | BREAK |
| Skipping the rest of an iteration | CONTINUE |
| Exiting the entire procedure | RETURN |
| Reacting to errors | TRY ... CATCH |
| Conditional branching | IF ... ELSE |
If your control flow can be expressed cleanly with the structured constructs above, prefer them — code with GOTO is harder to follow because execution can jump anywhere.
GOTO labeljumps unconditionally to a labeled point in the same batch or procedure.- Labels are identifiers ending with a colon; they live alongside other statements.
- Modern T-SQL replaces almost all GOTO uses with structured constructs —
WHILE,BREAK,CONTINUE,RETURN,TRY ... CATCH. - The legitimate niche: a single shared cleanup path at the bottom of a procedure that several validation failures all jump to.
- Avoid jumping backward for loops, jumping across nested control structures, or generally writing code where the next line of execution isn't obvious from reading top-to-bottom.