Error Handling in Stored Procedures

Robust error handling is crucial for ensuring the reliability and stability of your database applications. Stored procedures, being the primary mechanism for encapsulating business logic within the database, play a significant role in this. This tutorial explores various techniques for handling errors effectively within SQL Server stored procedures.

Why Error Handling is Important

Common Error Handling Techniques

1. Using `TRY...CATCH` Blocks

The `TRY...CATCH` construct is the most modern and recommended way to handle errors in SQL Server. It allows you to execute a block of T-SQL code and gracefully handle any runtime errors that occur within that block.

Tip: Always wrap your critical stored procedure logic within a `TRY` block.

CREATE PROCEDURE usp_UpdateProductPrice
    @ProductID INT,
    @NewPrice DECIMAL(10, 2)
AS
BEGIN
    SET NOCOUNT ON;

    BEGIN TRY
        UPDATE Production.Product
        SET ListPrice = @NewPrice
        WHERE ProductID = @ProductID;

        PRINT 'Product price updated successfully.';
    END TRY
    BEGIN CATCH
        -- Log the error or return error information
        DECLARE @ErrorMessage NVARCHAR(4000);
        DECLARE @ErrorSeverity INT;
        DECLARE @ErrorState INT;

        SELECT
            @ErrorMessage = ERROR_MESSAGE(),
            @ErrorSeverity = ERROR_SEVERITY(),
            @ErrorState = ERROR_STATE();

        -- In a real-world scenario, you would log this to an error table
        -- For demonstration, we'll just re-throw a custom error
        RAISERROR('Error updating product price. Details: %s', @ErrorSeverity, @ErrorState, @ErrorMessage);
    END CATCH
END;
            

2. Using `@@ERROR` (Legacy)

Before `TRY...CATCH`, the `@@ERROR` global variable was used to check for errors. While still functional, it's less flexible and often harder to manage than `TRY...CATCH`.

Note: `TRY...CATCH` is preferred for new development. `@@ERROR` can still be found in older codebases.

CREATE PROCEDURE usp_DeleteCustomer_Legacy
    @CustomerID INT
AS
BEGIN
    SET NOCOUNT ON;

    DELETE FROM Sales.Customer
    WHERE CustomerID = @CustomerID;

    IF @@ERROR <> 0
    BEGIN
        PRINT 'An error occurred while deleting the customer.';
        -- Handle error, perhaps rollback transaction or log
        RETURN -1; -- Indicate failure
    END
    ELSE
    BEGIN
        PRINT 'Customer deleted successfully.';
        RETURN 0; -- Indicate success
    END
END;
            

3. The `RAISERROR` Statement

The `RAISERROR` statement is used to raise a user-defined error. This is essential for signaling specific problems to the calling application or for providing custom error messages.

`RAISERROR` Syntax Overview

RAISERROR (message_string, severity, state [, argument1, argument2]... ) [WITH NOWAIT]

Important: Severity levels 19 through 25 are fatal errors and will terminate the connection. Use them with extreme caution.

4. Logging Errors

A critical part of error handling is logging. This allows you to diagnose issues after they occur. A common approach is to create a dedicated error logging table.

Example Error Logging Table:

Column Name Data Type Description
ErrorLogID INT IDENTITY(1,1) Primary key for the log entry.
ProcedureName NVARCHAR(255) Name of the stored procedure where the error occurred.
ErrorMessage NVARCHAR(4000) The detailed error message.
ErrorSeverity INT The severity level of the error.
ErrorState INT The error state.
ErrorDateTime DATETIME Timestamp when the error occurred.
UserName SYSNAME The user who executed the procedure.

-- Inside the CATCH block:
INSERT INTO dbo.ErrorLog (
    ProcedureName,
    ErrorMessage,
    ErrorSeverity,
    ErrorState,
    ErrorDateTime,
    UserName
)
VALUES (
    OBJECT_NAME(@@procid), -- Gets the name of the current procedure
    ERROR_MESSAGE(),
    ERROR_SEVERITY(),
    ERROR_STATE(),
    GETDATE(),
    SUSER_SNAME() -- Gets the login name
);
            

5. Error Handling within Transactions

When working with transactions, error handling becomes even more important. If an error occurs, you typically want to roll back the entire transaction to maintain atomicity.


CREATE PROCEDURE usp_ProcessOrder
    @CustomerID INT,
    @ProductID INT,
    @Quantity INT
AS
BEGIN
    SET NOCOUNT ON;

    BEGIN TRY
        BEGIN TRANSACTION;

        -- Step 1: Check inventory
        DECLARE @Stock INT;
        SELECT @Stock = StockQuantity FROM Production.ProductInventory WHERE ProductID = @ProductID;

        IF @Stock < @Quantity
        BEGIN
            RAISERROR('Insufficient stock for product.', 16, 1);
        END

        -- Step 2: Update inventory
        UPDATE Production.ProductInventory
        SET StockQuantity = StockQuantity - @Quantity
        WHERE ProductID = @ProductID;

        -- Step 3: Create sales order
        INSERT INTO Sales.SalesOrderHeader (CustomerID, OrderDate)
        VALUES (@CustomerID, GETDATE());

        DECLARE @OrderID INT = SCOPE_IDENTITY();

        INSERT INTO Sales.SalesOrderDetail (OrderID, ProductID, Quantity)
        VALUES (@OrderID, @ProductID, @Quantity);

        COMMIT TRANSACTION;
        PRINT 'Order processed successfully.';
    END TRY
    BEGIN CATCH
        IF @@TRANCOUNT > 0
            ROLLBACK TRANSACTION;

        -- Log the error
        INSERT INTO dbo.ErrorLog (ProcedureName, ErrorMessage, ErrorSeverity, ErrorState, ErrorDateTime, UserName)
        VALUES (OBJECT_NAME(@@procid), ERROR_MESSAGE(), ERROR_SEVERITY(), ERROR_STATE(), GETDATE(), SUSER_SNAME());

        -- Re-throw the error to the caller
        RAISERROR('An error occurred during order processing. See error log for details.', 16, 1);
    END CATCH
END;
            

Best Practices for Error Handling