Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

PL/SQL & T-SQL (Oracle and SQL Server)  Module 3 – T-SQL errors and transactions

T-SQL TRY...CATCH, THROW and RAISERROR

Catch T-SQL errors with TRY...CATCH and the ERROR functions, raise your own with THROW or RAISERROR, and see which errors no CATCH block receives.

  • Intermediate
  • 10 minutes
  • Examples run with oracle-free 23.26.3.0.0, mssql 17.0.5005.3, Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free core, Pro depth: 4 of 10 minutes free
Free: A TRY block hands its first error to the CATCH block. With a pass: 2 in-depth parts and 2 interview answers, about 6 minutes.

What you will learn

  • Catch run-time errors with TRY...CATCH and read ERROR_NUMBER, ERROR_MESSAGE, ERROR_LINE and ERROR_PROCEDURE
  • Explain why an error inside a transaction can still end in a partial commit
  • Report application errors with THROW, and choose between THROW and RAISERROR
  • Recognise the errors that a CATCH block at the same level never receives
  • Handle cleanup and logging in a CATCH block, then raise the original error again with THROW

Before you start

What you’ll learn · Before you start

What you will learn

  • Catch run-time errors with TRY...CATCH and read ERROR_NUMBER, ERROR_MESSAGE, ERROR_LINE and ERROR_PROCEDURE
  • Explain why an error inside a transaction can still end in a partial commit
  • Report application errors with THROW, and choose between THROW and RAISERROR
  • Recognise the errors that a CATCH block at the same level never receives
  • Handle cleanup and logging in a CATCH block, then raise the original error again with THROW

Before you start

On this page

A transfer of 5,000.00 inside BEGIN TRANSACTION fails on its debit with error 547, the credit after it runs anyway, and the COMMIT at the end keeps that credit: 5,000.00 created from nothing. T-SQL reports many run-time errors without stopping the batch, so code that must stop has to catch the error and decide itself what to undo.

You will catch errors with T-SQL TRY…CATCH and read what the ERROR_ functions report, reproduce the partial commit that an uncaught error leaves behind, raise your own errors with THROW and RAISERROR, meet the errors that no CATCH block at the same level receives, and write a CATCH block that cleans up, logs and raises the original error again.

A TRY block hands its first error to the CATCH block

T-SQL TRY…CATCH is a pair of blocks: BEGIN TRY … END TRY, followed at once by BEGIN CATCH … END CATCH. When a statement in the TRY block raises an error, the rest of the block is skipped and control passes to the first statement of the CATCH block; when nothing fails, the CATCH block is skipped. Microsoft’s page states what is caught: every run-time error with a error severity: The level from 0 to 25 that SQL Server gives every message: 10 or lower is information, 11 to 16 errors the user can correct, 17 to 19 software errors, and 20 or more fatal errors that stop the task. above 10 that does not close the database connection. An error inside a procedure called from the TRY block is caught too, unless the procedure has a TRY...CATCH of its own. A TRY...CATCH cannot span two batches and cannot be used inside a user-defined function. Step through a transfer that fails inside a procedure:

Interactive · message by message One error, from the TRY block to the CATCH block Step through the transfer of 5,000.00 from account 3 and see where control goes when the debit fails.
One error, from the TRY block to the CATCH block, step 6 of 6. Messages so far: TRY block to dbo.post_pair: EXEC, 5,000.00; dbo.post_pair to CATCH block: error 547.TRY blockno transaction opendbo.post_paircreditCATCH blockrolled backEXEC, 5,000.00error 547

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. The call starts. The TRY block opens a transaction and calls dbo.post_pair to move 5,000.00 from account 3 to account 1. The procedure has no TRY...CATCH of its own.
  2. The credit succeeds. The first UPDATE adds 5,000.00 to account 1, which now holds 7,500.00 inside the open transaction. Nothing is committed yet.
  3. The debit fails. The second UPDATE would take account 3 from 760.25 to below zero, so the CHECK constraint accounts_balance_ck refuses it with error 547, severity 16.
  4. Control jumps to CATCH. The error leaves the procedure and lands in the caller's CATCH block. The rest of the TRY block, its COMMIT and its PRINT, never runs.
  5. CATCH reads the details. Inside the CATCH block the ERROR_ functions describe the error. ERROR_NUMBER gives 547, ERROR_PROCEDURE dbo.post_pair and ERROR_LINE the line of the failed UPDATE.
  6. CATCH rolls back. @@TRANCOUNT is still 1, so the CATCH block rolls back and the credit disappears too: both balances are as they were. The error is not sent to the client unless the CATCH block sends it.

Inside the CATCH block, six functions describe the error, and the table after the script lists what each returns for the failed debit. Outside the CATCH block, each returns NULL, which the script’s last SELECT shows:

A procedure's error caught by its caller, with the ERROR_ functions SQL · basics/try_catch_basics.sql
-- Engine: SQL Server 2025 CU9 (17.0.5005.3); database lab with the practice schema. Client: sqlcmd.
-- An error inside a procedure, caught by the caller's TRY...CATCH, with the six ERROR_ functions. dbo.post_pair moves
-- money between two accounts; account 3 holds 760.25, so taking 5,000.00 from it breaks accounts_balance_ck.
CREATE OR ALTER PROCEDURE dbo.post_pair
  @from   int,
  @to     int,
  @amount decimal(14,2)
AS
BEGIN
  SET NOCOUNT ON;
  UPDATE dbo.accounts SET balance = balance + @amount WHERE account_id = @to;     -- the credit runs first
  UPDATE dbo.accounts SET balance = balance - @amount WHERE account_id = @from;   -- error 547 when @from is short
END;
GO
SET NOCOUNT ON;
BEGIN TRY
  BEGIN TRANSACTION;
  EXEC dbo.post_pair @from = 3, @to = 1, @amount = 5000.00;
  COMMIT TRANSACTION;                                     -- skipped: control left the TRY block at the error
  PRINT N'not printed';
END TRY
BEGIN CATCH
  IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;                -- undo the credit as well
  SELECT ERROR_NUMBER()    AS number,                     -- 547
         ERROR_SEVERITY()  AS severity,                   -- 16
         ERROR_STATE()     AS state,
         ERROR_PROCEDURE() AS in_procedure,               -- dbo.post_pair (with the schema since SQL Server 2017)
         ERROR_LINE()      AS at_line,                    -- the line of the second UPDATE inside the procedure
         ERROR_MESSAGE()   AS message;                    -- ... conflicted with the CHECK constraint "accounts_balance_ck" ...
END CATCH;
SELECT ERROR_NUMBER() AS outside_a_catch_block;           -- NULL
SELECT account_id, balance FROM dbo.accounts WHERE account_id IN (1, 3) ORDER BY account_id;   -- 2500.00, 760.25
DROP PROCEDURE dbo.post_pair;
GO
try_catch_basics.sql, run in the lab Python · try_catch_basics_session.py
"""try_catch_basics.sql in the lab: sqlcmd runs it as sa in the database lab, on a freshly loaded practice schema."""

from mssql_session import Lab

with Lab() as lab:
    lab.sqlcmd("basics/try_catch_basics.sql")

Output

$ sqlcmd -S localhost -U sa -C -d lab -i basics/try_catch_basics.sql
number severity state in_procedure  at_line message
------ -------- ----- ------------- ------- -----------------------------------------------------------------------------------------------------------------------------------------------------------------
   547       16     0 dbo.post_pair      12 The UPDATE statement conflicted with the CHECK constraint "accounts_balance_ck". The conflict occurred in database "lab", table "dbo.accounts", column 'balance'.
outside_a_catch_block
---------------------
                 NULL
account_id balance
---------- -------
         1 2500.00
         3  760.25

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 try_catch_basics_session.py

Function What it returns for the failed debit
ERROR_NUMBER() 547, the CHECK constraint violation
ERROR_SEVERITY() 16, an error the user can correct
ERROR_STATE() the state number the error was raised with
ERROR_PROCEDURE() dbo.post_pair, where the error happened (the schema is part of the name since SQL Server 2017), or NULL outside a procedure or trigger
ERROR_LINE() the line of the failed UPDATE inside the procedure
ERROR_MESSAGE() the full text, with the constraint, table and column filled in

One more rule changes how callers see errors: Microsoft notes that an error trapped by a CATCH block is not returned to the calling application. Unless the CATCH block sends it on, the client sees a batch that succeeded.

Sending an error on, and raising errors of your own, is the work of two statements. THROW raises an error with your own number and message, and Microsoft recommends it for new code. RAISERROR is the older statement, which formats its message and still runs in much existing code.

Checkpoint

Check: TRY...CATCH and the ERROR_ functions

  1. One question The second of four statements in a TRY block fails with error 547. What runs next?

    Choose one answer.

    Show the answer: One question

    Answer: The first statement of the CATCH block; statements three and four are skipped

    Control leaves the TRY block at its first error. What the CATCH block then does, roll back or not, is up to its code.

Partial commits, and raising errors with THROW and RAISERROR Pro: opens with a Pro, Premium or Ultimate pass

An error inside a transaction does not stop the transaction, and a procedure that finds a broken rule must raise an error of its own. This part reproduces the transfer that commits half of its work, then raises errors with THROW and RAISERROR and compares the two.

In this part:

  • An error inside a transaction ends only its own statement
  • THROW raises your own errors, and RAISERROR formats them

4 runnable examples (Python, SQL) · about 7 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Uncaught errors, and a CATCH block in the right order Pro: opens with a Pro, Premium or Ultimate pass

A CATCH block does not receive every error, and the order of its steps decides what the caller and the log see. This part lists the errors that escape it, then writes the block that copies the details, rolls back, logs and raises the original error again.

In this part:

  • Some errors never reach the CATCH block at their own level
  • Copy the details, roll back, log, then THROW again

1 diagram · 5 runnable examples (Python, SQL) · about 9 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Practise: follow the error to where it lands

Each question shows a statement that fails, or a CATCH block. Decide where control goes, what is kept and what the caller sees, then read why each answer is right or wrong:

Practice that adapts to you

Practise this lesson

3 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 3 What does ERROR_NUMBER() return when it is called outside a CATCH block?

    Choose one answer.

    Show the answer: Question 1 of 3

    Answer: NULL

    Read the ERROR_ functions inside the CATCH block, where they describe the error that was caught.

  2. Question 2 of 3 Can a scalar user-defined function contain TRY...CATCH?

    Choose one answer.

    Show the answer: Question 2 of 3

    Answer: No; TRY...CATCH cannot be used in a user-defined function

    Let a function's error reach the caller, and catch it in the procedure or batch that called the function.

  3. Question 3 of 3 A procedure called with no transaction open wraps its work in TRY...CATCH, and its CATCH block only rolls back. A transfer fails inside it. What does the calling application see?

    Choose one answer.

    Show the answer: Question 3 of 3

    Answer: A call that seems to succeed, because an error trapped by a CATCH block is not returned to the application

    A CATCH block that does not send the error on turns every failure into a silent success for the caller.

Interview questions

Warm-up (fresher to 2–5 years): what happens when a statement inside a TRY block fails, and what does the caller see?

The rest of the TRY block is skipped and control passes to the first statement of the CATCH block; when nothing fails, the CATCH block is skipped. The CATCH block receives every run-time error with a severity above 10 that does not close the connection, including one raised inside a procedure that the TRY block called, unless that procedure has a TRY...CATCH of its own. Inside the CATCH block, ERROR_NUMBER(), ERROR_SEVERITY(), ERROR_STATE(), ERROR_PROCEDURE(), ERROR_LINE() and ERROR_MESSAGE() describe the error, and outside it they return NULL. A caught error is not returned to the caller: unless the CATCH block sends it on, the client sees a batch that succeeded.

Two longer questions follow, one about a transfer that committed half of its work and one about an error log that stays empty; answer each before you open its first step:

  1. Mid to senior

    A transfer procedure runs a credit and a debit between BEGIN TRANSACTION and COMMIT, with no error handling. The finance team finds transfers where the credit happened and the debit did not, all on days when a balance check failed. Explain how that was possible, fix the procedure, and say how you would find every affected transfer.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A procedure's CATCH block inserts into dbo.error_log, rolls back and then runs RAISERROR(ERROR_MESSAGE(), 16, 1). After a week, the log table is empty although failures happened, and the calling application no longer recognises deadlocks and constraint errors. Explain both problems and rewrite the CATCH block.

    Model answer, Pro. First step and follow-ups

Flashcards · 3 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. Which errors does a TRY block hand to its CATCH block?

    Every run-time error of severity above 10 that does not close the database connection, including one raised inside a procedure the TRY block called, unless that procedure has a TRY...CATCH of its own. A TRY...CATCH cannot span two batches and cannot be used inside a user-defined function.

  2. What do the ERROR_ functions report, and where do they answer?

    ERROR_NUMBER, ERROR_SEVERITY, ERROR_STATE, ERROR_PROCEDURE (schema included since SQL Server 2017, NULL outside a procedure or trigger), ERROR_LINE and ERROR_MESSAGE. They answer inside the CATCH block only: outside it each returns NULL.

  3. A CATCH block logs the error and ends. What does the calling application see?

    A batch that succeeded. Microsoft notes that an error trapped by a CATCH block is not returned to the calling application unless the block sends it on.

Key takeaways

  • A TRY block passes its first run-time error of severity 11 or more to the CATCH block, where the ERROR_ functions describe it, and a caught error is not returned to the client.

Terms in this lesson

error severity
The level from 0 to 25 that SQL Server gives every message: 10 or lower is information, 11 to 16 errors the user can correct, 17 to 19 software errors, and 20 or more fatal errors that stop the task.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.