次の方法で共有


Transact-SQL での TRY...CATCH の使用

Transact-SQL コードのエラー処理には、Microsoft Visual C++ および Microsoft Visual C# の例外処理機能と同様の TRY...CATCH 構造を使用できます。TRY...CATCH 構造は、TRY ブロックと CATCH ブロックの 2 つの部分から構成されます。TRY ブロック内部の Transact-SQL ステートメントでエラー状態が検出されると、そのエラーを処理できる CATCH ブロックに制御が渡されます。

CATCH ブロックで例外の処理が終了すると、END CATCH ステートメントの直後の Transact-SQL ステートメントに処理が移ります。END CATCH ステートメントがストアド プロシージャまたはトリガの最後のステートメントである場合は、そのストアド プロシージャまたはトリガを呼び出したコードに制御が返されます。TRY ブロック内部の、エラーが発生したステートメントより後の Transact-SQL ステートメントは実行されません。

TRY ブロック内部にエラーがない場合、対応する END CATCH ステートメントの直後のステートメントに制御が渡ります。END CATCH ステートメントがストアド プロシージャまたはトリガの最後のステートメントである場合、そのストアド プロシージャまたはトリガを呼び出したステートメントに制御が渡されます。

TRY ブロックは BEGIN TRY ステートメントで始まり、END TRY ステートメントで終了します。BEGIN TRY と END TRY の間には、1 つ以上の Transact-SQL ステートメントを指定できます。

TRY ブロックの直後には CATCH ブロックを続ける必要があります。CATCH ブロックは BEGIN CATCH ステートメントで始まり、END CATCH ステートメントで終了します。Transact-SQL の各 TRY ブロックは、1 つの CATCH ブロックのみと対応します。

TRY...CATCH での処理

TRY...CATCH 構造を使用するときは、次のガイドラインおよび推奨事項を考慮してください。

  • TRY...CATCH 構造は、1 つのバッチ、ストアド プロシージャ、またはトリガ内に含める必要があります。たとえば、TRY ブロックをあるバッチに記述し、対応する CATCH ブロックを別のバッチに記述することはできません。次のスクリプトを実行するとエラーが発生します。

    BEGIN TRY
        SELECT *
            FROM sys.messages
            WHERE message_id = 21;
    END TRY
    GO
    -- The previous GO breaks the script into two batches,
    -- generating syntax errors. The script runs if this GO
    -- is removed.
    BEGIN CATCH
        SELECT ERROR_NUMBER() AS ErrorNumber;
    END CATCH;
    GO
    
  • TRY ブロックの直後に CATCH ブロックを続ける必要があります。

  • TRY...CATCH 構造は入れ子にすることができます。つまり、TRY...CATCH 構造は、他の TRY ブロックや CATCH ブロック内部に記述することができます。入れ子の TRY ブロックの中でエラーが発生した場合、その TRY ブロックに対応する CATCH ブロックにプログラムの制御が移ります。

  • ある CATCH ブロックの中で発生し得るエラーを処理するには、その CATCH ブロックの中に TRY...CATCH ブロックを記述します。

  • データベース エンジンで接続を閉じる原因となる重大度 20 以上のエラーは、TRY...CATCH ブロックで処理できません。ただし、接続が閉じられない限り、重大度 20 以上のエラーも TRY...CATCH ブロックで処理されます。

  • 重大度 10 以下のエラーは警告または情報メッセージと見なされ、TRY...CATCH ブロックでは処理されません。

  • アテンションが発生すると、バッチが TRY...CATCH 構造のスコープ内にあっても終了します。分散トランザクションが失敗したときに MS DTC (Microsoft 分散トランザクション コーディネータ) により送信されるアテンションもこれに該当します。MS DTC は分散トランザクションを管理します。

    注意注意

    TRY ブロックのスコープ内で分散トランザクションを実行していてエラーが発生した場合、対応する CATCH ブロックに実行制御が移ります。分散トランザクションはコミット不可能な状態になります。分散トランザクションを管理する Microsoft 分散トランザクション コーディネータにより、CATCH ブロック内の実行制御が中断される場合があります。エラーが発生すると、分散トランザクションに参加しているすべてのサーバーに対し非同期に通知が行われ、分散トランザクションに関係するすべてのタスクが終了します。この通知はアテンションとして送信されるので、TRY...CATCH 構造では処理されずにバッチが終了します。バッチの実行が完了すると、データベース エンジンによってコミット不可能なトランザクションがロールバックされます。トランザクションがコミット不可能な状態になったときにエラー メッセージが送信されなかった場合、バッチが完了すると、コミット不可能なトランザクションが検出されてロールバックされたことを示すエラー メッセージがクライアント アプリケーションに送信されます。分散トランザクションの詳細については、「分散トランザクション (データベース エンジン)」を参照してください。

エラー関数

TRY...CATCH では、次のエラー関数を使用してエラー情報を取得します。

  • ERROR_NUMBER() はエラー番号を返します。

  • ERROR_MESSAGE() はエラー メッセージのテキストの全文を返します。テキストには、長さ、オブジェクト名、時刻など、置換可能なパラメータに指定された値が含まれます。

  • ERROR_SEVERITY() はエラーの重大度を返します。

  • ERROR_STATE() はエラー状態番号を返します。

  • ERROR_LINE() はルーチン内部でエラーが発生した行の番号を返します。

  • ERROR_PROCEDURE() はエラーが発生したストアド プロシージャまたはトリガの名前を返します。

TRY...CATCH 構造の CATCH ブロックのスコープ内であれば、どこからでもこれらの関数を使用してエラー情報を取得できます。CATCH ブロックのスコープ外部からエラー関数を呼び出すと、NULL が返されます。エラー関数は、ストアド プロシージャ内部から参照できます。また、参照元のストアド プロシージャが CATCH ブロック内で実行されている場合は、エラー関数でエラー情報を取得できます。これを行うことにより、すべての CATCH ブロックでエラー処理コードを繰り返す必要はなくなります。次のコード例では、TRY ブロック内の SELECT ステートメントで 0 除算エラーが発生します。エラー処理が行われる CATCH ブロックでは、エラー情報を返すためにストアド プロシージャが使用されます。

USE AdventureWorks;
GO

-- Verify that the stored procedure does not exist.
IF OBJECT_ID ('usp_GetErrorInfo', 'P') IS NOT NULL
    DROP PROCEDURE usp_GetErrorInfo;
GO

-- Create a procedure to retrieve error information.
CREATE PROCEDURE usp_GetErrorInfo
AS
    SELECT 
        ERROR_NUMBER() AS ErrorNumber,
        ERROR_SEVERITY() AS ErrorSeverity,
        ERROR_STATE() as ErrorState,
        ERROR_PROCEDURE() as ErrorProcedure,
        ERROR_LINE() as ErrorLine,
        ERROR_MESSAGE() as ErrorMessage;
GO

BEGIN TRY
    -- Generate divide-by-zero error.
    SELECT 1/0;
END TRY
BEGIN CATCH
    -- Execute the error retrieval routine.
    EXECUTE usp_GetErrorInfo;
END CATCH;
GO

コンパイル時およびステートメントレベルの再コンパイル時のエラー

エラーが TRY...CATCH 構造と同一の実行レベルで発生しても、TRY...CATCH では処理されないエラーが 2 種類あります。

  • コンパイル エラー (バッチの実行を不可能にする構文エラーなど)。

  • ステートメントレベルの再コンパイルで発生するエラー (コンパイル後の名前の遅延解決により発生するオブジェクト名の解決エラーなど)。

TRY...CATCH 構造が含まれたバッチ、ストアド プロシージャ、またはトリガで上記のいずれかのエラーが発生しても、TRY...CATCH 構造でエラーは処理されません。このようなエラーは、エラーが発生元ルーチンを呼び出したアプリケーションまたはバッチに返されます。たとえば、次のコード例では、構文エラーの原因となる SELECT ステートメントを示します。このコードを SQL Server Management Studio クエリ エディタで実行しても、バッチのコンパイルに失敗するので実行は開始されません。エラーは、クエリ エディタに返され、TRY...CATCH ではキャッチされません。

USE AdventureWorks;
GO

BEGIN TRY
    -- This PRINT statement will not run because the batch
    -- does not begin execution.
    PRINT N'Starting execution';

    -- This SELECT statement contains a syntax error that
    -- stops the batch from compiling successfully.
    SELECT ** FROM HumanResources.Employee;
END TRY
BEGIN CATCH
    SELECT 
        ERROR_NUMBER() AS ErrorNumber,
        ERROR_MESSAGE() AS ErrorMessage;
END CATCH;
GO

ステートメントレベルの再コンパイルで発生するエラーの場合、上の例の構文エラーとは異なりバッチのコンパイルが停止することはありませんが、ステートメントの再コンパイルが失敗した直後にバッチが終了します。たとえば、2 つのステートメントから構成されるバッチの 2 番目のステートメントで、存在しないテーブルを参照しているとします。この場合、名前の遅延解決によってバッチは正常にコンパイルされ、ステートメントが再コンパイルされるまでは、問題のテーブルをクエリ プランにバインドすることなく実行が開始されます。存在しないテーブルを参照するステートメントに到達するとバッチは実行を停止し、エラーを返します。この種のエラーは、エラーの発生場所と同一の実行レベルの TRY...CATCH 構造では処理されません。次の例でその動作を示します。

USE AdventureWorks;
GO

BEGIN TRY
    -- This PRINT statement will run because the error
    -- occurs at the SELECT statement.
    PRINT N'Starting execution';

    -- This SELECT statement will generate an object name
    -- resolution error because the table does not exist.
    SELECT * FROM NonExistentTable;
END TRY
BEGIN CATCH
    SELECT 
        ERROR_NUMBER() AS ErrorNumber,
        ERROR_MESSAGE() AS ErrorMessage;
END CATCH;
GO

コンパイル時またはステートメントレベルの再コンパイル時に発生するエラーを TRY...CATCH を使用して処理するには、エラーが発生するコードを TRY ブロック内部の独立したバッチで実行します。これを行うには、たとえば、コードをストアド プロシージャに記述するか、sp_executesql を使用して動的な Transact-SQL ステートメントを実行します。これにより、エラーの発生場所よりも高い実行レベルのエラーを TRY...CATCH でキャッチできます。たとえば、次のコードでは、オブジェクト名の解決エラーが発生するストアド プロシージャを示します。TRY...CATCH 構造が含まれたバッチはストアド プロシージャより高いレベルで実行され、低いレベルで発生したエラーはキャッチされます。

USE AdventureWorks;
GO

-- Verify that the stored procedure does not already exist.
IF OBJECT_ID ('usp_MyError', 'P') IS NOT NULL
    DROP PROCEDURE usp_MyError;
GO

CREATE PROCEDURE usp_MyError
AS
    -- This SELECT statement will generate
    -- an object name resolution error.
    SELECT * FROM NonExistentTable;
GO

BEGIN TRY
    -- Run the stored procedure.
    EXECUTE usp_MyError;
END TRY
BEGIN CATCH
    SELECT 
        ERROR_NUMBER() AS ErrorNumber,
        ERROR_MESSAGE() AS ErrorMessage;
END CATCH;
GO

以下に結果セットを示します。

ErrorNumber ErrorMessage
----------- ---------------------------------------
208         Invalid object name 'NonExistentTable'.

詳細については、「名前の遅延解決とコンパイル」、および「実行プランのキャッシュと再利用」の「実行プランのキャッシュと再利用」を参照してください。

コミット不可能なトランザクション

TRY...CATCH 構造の内部で、トランザクションが開いたままコミットできない状態になる場合があります。このようなトランザクションは、データの変更、セーブポイントまでのロールバックの試行など、トランザクション ログへの書き込みが発生するアクションを実行できません。ただし、この状態では、トランザクションによって取得されたロックが保たれ、接続も開いたままになります。ROLLBACK ステートメントが発行されるか、バッチが終了して、データベース エンジンによって自動的にトランザクションがロールバックされるまで、トランザクションの影響は元に戻りません。トランザクションがコミット不可能な状態になったときにエラー メッセージが送信されなかった場合、バッチが完了すると、コミットできないトランザクションが検出されてロールバックされたことを示すエラー メッセージがクライアント アプリケーションに送信されます。

エラーが発生して、トランザクションを終了するしかないという状況の場合、トランザクションは TRY ブロック内部でコミット不可能な状態になります。たとえば、DDL (データ定義言語) ステートメント (CREATE TABLE など) によるエラーや SET XACT_ABORT が ON に設定されているときに発生するエラーにより、多くの場合、TRY ブロック外部ではトランザクションが終了しますが、TRY ブロック内部ではトランザクションがコミット不可能な状態になります。

CATCH ブロック内のコードでは、XACT_STATE 関数を使用してトランザクションの状態についてテストするようにしてください。セッションにコミット不可能なトランザクションがある場合、XACT_STATE が -1 を返します。XACT_STATE が -1 を返す場合は、CATCH ブロックでログへの書き込みを発生させるアクションを実行できません。次のコード例を実行すると、DDL ステートメントによるエラーが発生します。そこで XACT_STATE を使用してトランザクションの状態をテストし、最適な処置を施します。

USE AdventureWorks;
GO

-- Verify that the table does not exist.
IF OBJECT_ID (N'my_books', N'U') IS NOT NULL
    DROP TABLE my_books;
GO

-- Create table my_books.
CREATE TABLE my_books
    (
    Isbn        int PRIMARY KEY,
    Title       NVARCHAR(100)
    );
GO

BEGIN TRY
    BEGIN TRANSACTION;
        -- This statement will generate an error because the 
        -- column author does not exist in the table.
        ALTER TABLE my_books
            DROP COLUMN author;
    -- If the DDL statement succeeds, commit the transaction.
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    SELECT
        ERROR_NUMBER() as ErrorNumber,
        ERROR_MESSAGE() as ErrorMessage;

    -- Test XACT_STATE for 1 or -1.
    -- XACT_STATE = 0 means there is no transaction and
    -- a commit or rollback operation would generate an error.

    -- Test whether the transaction is uncommittable.
    IF (XACT_STATE()) = -1
    BEGIN
        PRINT
            N'The transaction is in an uncommittable state. ' +
            'Rolling back transaction.'
        ROLLBACK TRANSACTION;
    END;

    -- Test whether the transaction is active and valid.
    IF (XACT_STATE()) = 1
    BEGIN
        PRINT
            N'The transaction is committable. ' +
            'Committing transaction.'
        COMMIT TRANSACTION;   
    END;
END CATCH;
GO

デッドロックの処理

TRY...CATCH はデッドロックの処理に使用できます。デッドロックの対象になったことを示す 1205 エラーを CATCH ブロックでキャッチして、スレッドのロックが解除されるまでトランザクションをロールバックできます。デッドロックの詳細については、「デッドロック」を参照してください。

次の例では、TRY...CATCH を使用してデッドロックを処理する方法を示します。次に示す冒頭部分では、デッドロック状態を再現するためのテーブル、およびエラー情報を出力するためのストアド プロシージャを作成します。

USE AdventureWorks;
GO

-- Verify that the table does not exist.
IF OBJECT_ID (N'my_sales',N'U') IS NOT NULL
    DROP TABLE my_sales;
GO

-- Create and populate the table for deadlock simulation.
CREATE TABLE my_sales 
    (
    Itemid       INT PRIMARY KEY,
    Sales        INT not null
    );
GO

INSERT my_sales (itemid, sales) VALUES (1, 1);
INSERT my_sales (itemid, sales) VALUES (2, 1);
GO
  
-- Verify that the stored procedure for error printing
-- does not exist.
IF OBJECT_ID (N'usp_MyErrorLog',N'P') IS NOT NULL
    DROP PROCEDURE usp_MyErrorLog;
GO

-- Create a stored procedure for printing error information.
CREATE PROCEDURE usp_MyErrorLog
AS
    PRINT 
        'Error ' + CONVERT(VARCHAR(50), ERROR_NUMBER()) +
        ', Severity ' + CONVERT(VARCHAR(5), ERROR_SEVERITY()) +
        ', State ' + CONVERT(VARCHAR(5), ERROR_STATE()) + 
        ', Line ' + CONVERT(VARCHAR(5), ERROR_LINE());
    PRINT 
        ERROR_MESSAGE();
GO

次のセッション 1 およびセッション 2 で使用するコード スクリプトは、2 つの SQL Server Management Studio 接続でそれぞれ同時に実行されます。いずれもテーブル内の同一行を更新するセッションです。一方のセッションは最初の実行時に更新操作に成功しますが、もう一方はデッドロックの対象として選択されます。デッドロックの対象になったことを示すエラーが発生することで実行制御が CATCH ブロックに移動し、トランザクションがコミット不可能な状態になります。CATCH ブロック内部では、デッドロックの対象になったトランザクションをロールバックし、テーブルの更新を、更新が成功するか最大再試行回数に達するかのいずれかが先に起こるまで繰り返すことができます。

セッション 1

セッション 2

USE AdventureWorks;
GO
-- Declare and set variable
-- to track number of retries
-- to try before exiting.
DECLARE @retry INT;
SET @retry = 5;
-- Keep trying to update 
-- table if this task is 
-- selected as the deadlock 
-- victim.
WHILE (@retry > 0)
BEGIN
    BEGIN TRY
        BEGIN TRANSACTION;
    
        UPDATE my_sales
        SET sales = sales + 1
        WHERE itemid = 1;
        WAITFOR DELAY '00:00:13';
    
        UPDATE my_sales
        SET sales = sales + 1
        WHERE itemid = 2;
        SET @retry = 0;
        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH 
        -- Check error number.
        -- If deadlock victim error,
        -- then reduce retry count
        -- for next update retry. 
        -- If some other error
        -- occurred, then exit
        -- retry WHILE loop.
        IF (ERROR_NUMBER() = 1205)
            SET @retry = @retry - 1;
        ELSE
            SET @retry = -1;
        -- Print error information.
        EXECUTE usp_MyErrorLog;
  
        IF XACT_STATE() <> 0
            ROLLBACK TRANSACTION;
    END CATCH;
END; -- End WHILE loop.
GO
USE AdventureWorks;
GO
-- Declare and set variable
-- to track number of retries
-- to try before exiting.
DECLARE @retry INT;
SET @retry = 5;
--Keep trying to update 
-- table if this task is 
-- selected as the deadlock 
-- victim.
WHILE (@retry > 0)
BEGIN
    BEGIN TRY
       BEGIN TRANSACTION;
    
        UPDATE my_sales
        SET sales = sales + 1
        WHERE itemid = 2;
        WAITFOR DELAY '00:00:07';
    
        UPDATE my_sales
        SET sales = sales + 1
        WHERE itemid = 1;
        SET @retry = 0;
        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH 
        -- Check error number.
        -- If deadlock victim error,
        -- then reduce retry count
        -- for next update retry. 
        -- If some other error
        -- occurred, then exit
        -- retry WHILE loop.
        IF (ERROR_NUMBER() = 1205)
            SET @retry = @retry - 1;
        ELSE
            SET @retry = -1;
        -- Print error information.
        EXECUTE usp_MyErrorLog;
  
        IF XACT_STATE() <> 0
            ROLLBACK TRANSACTION;
    END CATCH;
END; -- End WHILE loop.
GO

TRY...CATCH での RAISERROR の指定

TRY...CATCH 構造の TRY ブロックまたは CATCH ブロックのいずれかで、RAISERROR を使用してエラー処理の動作を変えることができます。

TRY ブロック内部で実行される重大度 11 ~ 19 の RAISERROR により、対応する CATCH ブロックに制御が移ります。CATCH ブロック内部で実行される重大度 11 ~ 19 の RAISERROR により、呼び出し元のアプリケーションまたはバッチにエラーが返されます。このように、RAISERROR を使用することで、CATCH ブロックが実行される原因になったエラーについて、呼び出し元に情報を返すことができます。TRY...CATCH のエラー関数が返す元のエラー番号などのエラー情報は RAISERROR メッセージでキャプチャできますが、RAISERROR のエラー番号は 50,000 以上にする必要があります。

重大度 10 以下の RAISERROR は、CATCH ブロックを呼び出すことなく呼び出し元のバッチまたはアプリケーションに情報メッセージを返します。

重大度 20 以上の RAISERROR は、CATCH ブロックを呼び出すことなくデータベース接続を終了します。

次のコード例では、CATCH ブロック内部で RAISERROR を使用して、呼び出し元のアプリケーションまたはバッチに元のエラー情報を返す方法を示します。ストアド プロシージャ usp_GenerateError では、制約違反エラーを生成する TRY ブロック内部の DELETE ステートメントを実行します。エラーが発生すると、usp_GenerateError 内の対応する CATCH ブロックに実行制御が移ります。ブロック内では、ストアド プロシージャ usp_RethrowError が実行され、RAISERROR を使用して制約違反エラーの情報が作成されます。RAISERROR によって生成されたエラーは usp_GenerateError を実行した呼び出し元のバッチに返され、呼び出し元のバッチの対応する CATCH ブロックに実行制御が移ります。

注意注意

RAISERROR では、状態が 1 ~ 127 のエラーのみ生成できます。データベース エンジンによって状態 0 のエラーが発生する可能性があるため、ERROR_STATE によって返されるエラー状態をチェックしてから、この値を RAISERROR の状態パラメータに渡すことをお勧めします。

USE AdventureWorks;
GO

-- Verify that stored procedure does not exist.
IF OBJECT_ID (N'usp_RethrowError',N'P') IS NOT NULL
    DROP PROCEDURE usp_RethrowError;
GO

-- Create the stored procedure to generate an error using 
-- RAISERROR. The original error information is used to
-- construct the msg_str for RAISERROR.
CREATE PROCEDURE usp_RethrowError AS
    -- Return if there is no error information to retrieve.
    IF ERROR_NUMBER() IS NULL
        RETURN;

    DECLARE 
        @ErrorMessage    NVARCHAR(4000),
        @ErrorNumber     INT,
        @ErrorSeverity   INT,
        @ErrorState      INT,
        @ErrorLine       INT,
        @ErrorProcedure  NVARCHAR(200);

    -- Assign variables to error-handling functions that 
    -- capture information for RAISERROR.
    SELECT 
        @ErrorNumber = ERROR_NUMBER(),
        @ErrorSeverity = ERROR_SEVERITY(),
        @ErrorState = ERROR_STATE(),
        @ErrorLine = ERROR_LINE(),
        @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

    -- Build the message string that will contain original
    -- error information.
    SELECT @ErrorMessage = 
        N'Error %d, Level %d, State %d, Procedure %s, Line %d, ' + 
            'Message: '+ ERROR_MESSAGE();

    -- Raise an error: msg_str parameter of RAISERROR will contain
    -- the original error information.
    RAISERROR 
        (
        @ErrorMessage, 
        @ErrorSeverity, 
        1,               
        @ErrorNumber,    -- parameter: original error number.
        @ErrorSeverity,  -- parameter: original error severity.
        @ErrorState,     -- parameter: original error state.
        @ErrorProcedure, -- parameter: original error procedure name.
        @ErrorLine       -- parameter: original error line number.
        );
GO

-- Verify that stored procedure does not exist.
IF OBJECT_ID (N'usp_GenerateError',N'P') IS NOT NULL
    DROP PROCEDURE usp_GenerateError;
GO

-- Create a stored procedure that generates a constraint violation
-- error. The error is caught by the CATCH block where it is 
-- raised again by executing usp_RethrowError.
CREATE PROCEDURE usp_GenerateError 
AS 
    BEGIN TRY
        -- A FOREIGN KEY constraint exists on the table. This 
        -- statement will generate a constraint violation error.
        DELETE FROM Production.Product
            WHERE ProductID = 980;
    END TRY
    BEGIN CATCH
        -- Call the procedure to raise the original error.
        EXEC usp_RethrowError;
    END CATCH;
GO

-- In the following batch, an error occurs inside 
-- usp_GenerateError that invokes the CATCH block in
-- usp_GenerateError. RAISERROR inside this CATCH block
-- generates an error that invokes the outer CATCH
-- block in the calling batch.
BEGIN TRY  -- outer TRY
    -- Call the procedure to generate an error.
    EXECUTE usp_GenerateError;
END TRY
BEGIN CATCH  -- Outer CATCH
    SELECT
        ERROR_NUMBER() as ErrorNumber,
        ERROR_MESSAGE() as ErrorMessage;
END CATCH;
GO

実行フローの変更

実行フローを変更するためには、TRY ブロックまたは CATCH ブロック内部で GOTO を使用できます。GOTO は TRY ブロックまたは CATCH ブロックを終了する目的にも使用できますが、TRY ブロックまたは CATCH ブロックに入るためには使用できません。

AdventureWorks サンプル データベースのエラー処理ソリューション

AdventureWorks サンプル データベースのエラー処理ソリューションは、後でクエリや分析の対象にすることができる、TRY...CATCH 構造の CATCH ブロックでキャッチしたエラーに関する情報をログに書き込むようデザインされています。

dbo.ErrorLog テーブル

ErrorLog テーブルには、エラー番号、エラーの重大度、エラーの状態、エラーが発生したストアド プロシージャまたはトリガの名前、エラーが発生した行の番号、およびエラー メッセージのテキストの全文が記録されます。また、エラーの発生日時、およびエラーが発生したルーチンを実行したユーザーの名前も記録されます。TRY...CATCH 構造の CATCH ブロックのスコープ内でストアド プロシージャ uspLogError が実行されると、このテーブルに情報が格納されます。詳細については、「ErrorLog テーブル (AdventureWorks)」を参照してください。

dbo.uspLogError

ストアド プロシージャ uspLogError が実行されると、TRY...CATCH 構造の CATCH ブロックに実行制御が移る原因になったエラーに関する情報が ErrorLog テーブルに書き込まれます。uspLogErrorErrorLog テーブルにエラー情報を挿入するために必要な条件は次のとおりです。

  • uspLogError を CATCH ブロックのスコープ内部で実行する。

  • 現在のトランザクションがコミット不可能な状態である場合、uspLogError の実行前にロールバックする。

uspLogError の出力パラメータ @ErrorLogID は、uspLogError により ErrorLog テーブルに挿入される行の ErrorLogID を返します。@ErrorLogID の既定値は 0 です。次の例は、uspLogError のコードです。詳細については、「AdventureWorks のストアド プロシージャ」を参照してください。

CREATE PROCEDURE [dbo].[uspLogError] 
    @ErrorLogID [int] = 0 OUTPUT  -- Contains the ErrorLogID of the row inserted
                                  -- by uspLogError in the ErrorLog table.

AS
BEGIN
    SET NOCOUNT ON;

    -- Output parameter value of 0 indicates that error 
    -- information was not logged.
    SET @ErrorLogID = 0;

    BEGIN TRY
        -- Return if there is no error information to log.
        IF ERROR_NUMBER() IS NULL
            RETURN;

        -- Return if inside an uncommittable transaction.
        -- Data insertion/modification is not allowed when 
        -- a transaction is in an uncommittable state.
        IF XACT_STATE() = -1
        BEGIN
            PRINT 'Cannot log error since the current transaction is in an uncommittable state. ' 
                + 'Rollback the transaction before executing uspLogError in order to successfully log error information.';
            RETURN;
        END;

        INSERT [dbo].[ErrorLog] 
            (
            [UserName], 
            [ErrorNumber], 
            [ErrorSeverity], 
            [ErrorState], 
            [ErrorProcedure], 
            [ErrorLine], 
            [ErrorMessage]
            ) 
        VALUES 
            (
            CONVERT(sysname, CURRENT_USER), 
            ERROR_NUMBER(),
            ERROR_SEVERITY(),
            ERROR_STATE(),
            ERROR_PROCEDURE(),
            ERROR_LINE(),
            ERROR_MESSAGE()
            );

        -- Pass back the ErrorLogID of the row inserted
        SELECT @ErrorLogID = @@IDENTITY;
    END TRY
    BEGIN CATCH
        PRINT 'An error occurred in stored procedure uspLogError: ';
        EXECUTE [dbo].[uspPrintError];
        RETURN -1;
    END CATCH
END; 

dbo.uspPrintError

ストアド プロシージャ uspPrintError は、TRY...CATCH 構造の CATCH ブロックに実行制御が移る原因になったエラーに関する情報を出力します。uspPrintError は CATCH ブロックのスコープ内で実行しないと、エラー情報を出力することなく返されます。次の例は、uspPrintError のコードです。詳細については、「AdventureWorks のストアド プロシージャ」を参照してください。

CREATE PROCEDURE [dbo].[uspPrintError] 
AS
BEGIN
    SET NOCOUNT ON;

    -- Print error information. 
    PRINT 'Error ' + CONVERT(varchar(50), ERROR_NUMBER()) +
          ', Severity ' + CONVERT(varchar(5), ERROR_SEVERITY()) +
          ', State ' + CONVERT(varchar(5), ERROR_STATE()) + 
          ', Procedure ' + ISNULL(ERROR_PROCEDURE(), '-') + 
          ', Line ' + CONVERT(varchar(5), ERROR_LINE());
    PRINT ERROR_MESSAGE();
END;

エラー処理の例

次の例では、AdventureWorks のエラー処理ソリューションについて説明します。TRY ブロック内部のコードは、Production.Product テーブルの ProductID 980 というレコードを削除します。このテーブルにある FOREIGN KEY 制約により、DELETE ステートメントは成功せず、制約違反エラーが発生します。このエラーが発生すると、CATCH ブロックに実行制御が移ります。CATCH ブロック内部では次のアクションが行われます。

  • uspPrintError によりエラー情報が出力されます。

  • トランザクションをロールバックした後、uspLogError によって ErrorLog テーブルにエラー情報が書き込まれ、@ErrorLogID OUTPUT パラメータに挿入される行の ErrorLogID が返されます。

USE AdventureWorks;
GO

-- Variable to store ErrorLogID value of the row
-- inserted in the ErrorLog table by uspLogError 
DECLARE @ErrorLogID INT;

BEGIN TRY
    BEGIN TRANSACTION;

    -- A FOREIGN KEY constraint exists on this table. This 
    -- statement will generate a constraint violation error.
    DELETE FROM Production.Product
        WHERE ProductID = 980;

    -- If the delete operation succeeds, commit the transaction.
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    -- Call procedure to print error information.
    EXECUTE dbo.uspPrintError;

    -- Roll back any active or uncommittable transactions before
    -- inserting information in the ErrorLog.
    IF XACT_STATE() <> 0
    BEGIN
        ROLLBACK TRANSACTION;
    END

    EXECUTE dbo.uspLogError @ErrorLogID = @ErrorLogID OUTPUT;
END CATCH; 

-- Retrieve logged error information.
SELECT * FROM dbo.ErrorLog WHERE ErrorLogID = @ErrorLogID;
GO

入れ子になったエラー処理の例

次の例では、入れ子になった TRY...CATCH 構造を示します。

BEGIN TRY
    BEGIN TRY
        SELECT CAST('invalid_date' AS datetime)
    END TRY
    BEGIN CATCH 
        PRINT 'Inner TRY error number: ' +    
            CONVERT(varchar,ERROR_NUMBER()) + ' on line: ' +
            CONVERT(varchar, ERROR_LINE())
    END CATCH
    SELECT CAST('invalid_int' AS int)
END TRY
BEGIN CATCH
    PRINT 'Outer TRY error mumber: ' + CONVERT(varchar,ERROR_NUMBER())+ 
            ' on line: ' + CONVERT(varchar, ERROR_LINE())
END CATCH

以下に結果セットを示します。

Inner TRY error number: 241 on line: 3

Outer TRY error number: 245 on line: 9