Error Handling in SQL

Effective error handling is crucial for building robust and reliable SQL applications. It allows you to gracefully manage unexpected situations, provide meaningful feedback to users, and maintain data integrity.

SQL Server can encounter various types of errors during query execution:

SQL Server provides several ways to report and handle errors:

These statements are used to explicitly raise errors or custom messages within your T-SQL code.

RAISERROR is a more versatile statement, allowing you to specify severity, state, and even insert custom parameters into messages. It's been a staple for many years.


-- Example using RAISERROR
IF EXISTS (SELECT 1 FROM YourTable WHERE SomeColumn IS NULL)
BEGIN
    RAISERROR('A required field cannot be null. Please check your input.', 16, 1);
    -- Additional error details can be provided as arguments
    -- RAISERROR('Value %s is out of range (0-100).', 16, 1, '500');
END
            

THROW, introduced in SQL Server 2012, is a simpler and more modern way to raise an error. It re-throws the last error encountered or allows you to raise a specific error with its number and message.


-- Example using THROW (re-throwing last error)
BEGIN TRY
    -- Some operation that might fail
    SELECT 1 / 0;
END TRY
BEGIN CATCH
    THROW; -- Re-throws the original error from the CATCH block
END CATCH

-- Example using THROW with custom error
BEGIN TRY
    -- Some operation
    IF @SomeCondition = 'Invalid'
        THROW 50001, 'Invalid input parameter provided.', 1;
END TRY
BEGIN CATCH
    -- Handle the custom error or re-throw
    PRINT ERROR_MESSAGE();
END CATCH
            

Note: While RAISERROR is still supported, THROW is generally recommended for new development due to its simpler syntax and better integration with the TRY...CATCH block.

The TRY...CATCH construct provides a structured way to handle runtime errors gracefully within T-SQL stored procedures and batches.


BEGIN TRY
    -- Code that might cause an error
    INSERT INTO Products (ProductName, Price) VALUES ('Widget', -10.00); -- Violates CHECK constraint
    PRINT 'Insert successful!';
END TRY
BEGIN CATCH
    -- Error handling logic
    DECLARE @ErrorMessage NVARCHAR(4000);
    DECLARE @ErrorSeverity INT;
    DECLARE @ErrorState INT;

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

    -- Log the error to an error log table (recommended)
    INSERT INTO ErrorLog (ErrorMessage, ErrorSeverity, ErrorState, ErrorTime)
    VALUES (@ErrorMessage, @ErrorSeverity, @ErrorState, GETDATE());

    -- Re-throw the error to the calling application
    RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState);
    -- Or use THROW for newer versions:
    -- THROW @ErrorSeverity, @ErrorMessage, @ErrorState;
END CATCH
            

Within a CATCH block, several built-in functions can be used to retrieve details about the error that occurred:

Tip: Always use ERROR_LINE() in conjunction with your code to pinpoint the exact location of the error during debugging.

Here's an example of a stored procedure that demonstrates good error handling practices:


CREATE PROCEDURE usp_UpdateProductPrice
    @ProductID INT,
    @NewPrice DECIMAL(10, 2)
AS
BEGIN
    SET NOCOUNT ON; -- Prevents sending DONE_IN_PROC messages

    -- Basic input validation
    IF @ProductID IS NULL OR @NewPrice IS NULL
    BEGIN
        THROW 51000, 'ProductID and NewPrice cannot be NULL.', 1;
        RETURN; -- Exit if validation fails
    END

    IF @NewPrice <= 0
    BEGIN
        THROW 51001, 'NewPrice must be a positive value.', 1;
        RETURN; -- Exit if validation fails
    END

    BEGIN TRY
        BEGIN TRANSACTION; -- Start transaction

        UPDATE Products
        SET Price = @NewPrice
        WHERE ProductID = @ProductID;

        IF @@ROWCOUNT = 0
        BEGIN
            -- No rows were updated, perhaps the ProductID doesn't exist.
            THROW 51002, 'Product with the specified ID not found.', 1;
        END

        COMMIT TRANSACTION; -- Commit if everything succeeded
        PRINT 'Product price updated successfully.';

    END TRY
    BEGIN CATCH
        -- Rollback transaction if an error occurred
        IF @@TRANCOUNT > 0
            ROLLBACK TRANSACTION;

        -- Get error details
        DECLARE @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE();
        DECLARE @ErrorSeverity INT = ERROR_SEVERITY();
        DECLARE @ErrorState INT = ERROR_STATE();

        -- Log the error
        INSERT INTO ErrorLog (ErrorMessage, ErrorSeverity, ErrorState, ErrorProcedure, ErrorLine, ErrorTime)
        VALUES (@ErrorMessage, @ErrorSeverity, @ErrorState, ERROR_PROCEDURE(), ERROR_LINE(), GETDATE());

        -- Re-throw the error to the caller
        THROW; -- Re-throws the original error caught
    END CATCH
END
GO
            

By implementing these strategies, you can significantly improve the reliability and maintainability of your SQL Server applications.