web-dev-qa-db-ja.com

T-SQL:更新トリガーでカーソルを閉じる/割り当て解除する適切な方法

次のようなトリガーがあるとしましょう。

CREATE TRIGGER trigger1
   ON [dbo].[table1] 
   AFTER UPDATE
AS 
BEGIN               
    --declare some vars
    DECLARE @Col1 SMALLINT 
    DECLARE @Col1 TINYINT 

    --declare cursor        
    DECLARE Cursor1 CURSOR FOR 
    SELECT Col1, Col2 FROM INSERTED             

    --do the job
    OPEN Cursor1
    FETCH NEXT FROM Cursor1 INTO @Col1, @Col2

    WHILE @@FETCH_STATUS = 0
    BEGIN
        IF ...something...
        BEGIN           
            EXEC myProc1 @param1 = @Col1, @Param2 = @Col2
        END             
        ELSE
        IF ...something else...
        BEGIN           
            EXEC myProc2 @param1 = @Col1, @Param2 = @Col2
        END     

        FETCH NEXT FROM Cursor1 INTO @Col1, @Col2               
    END

    --clean it up       
    CLOSE Cursor1
    DEALLOCATE Cursor1                  
END

Cursor1が常に閉じられ、割り当てが解除されていることを確認したいと思います。 myProc1またはmyProc2でさえ失敗します。

Try/catchブロックを使用しますか?

17
Novitzky

はい、TRY/CATCHを使用しますが、後で割り当てを解除するなどしてください。残念ながら、SQLServerには最終的なものはありません。

ただし、これを別のtry/catchでラップすることをお勧めします

CREATE TRIGGER trigger1 ON [dbo].[table1] AFTER UPDATE
AS 
BEGIN                           
    --declare some vars
    DECLARE @Col1 SMALLINT, @Col1 TINYINT 

    BEGIN TRY
        --declare cursor            
        DECLARE Cursor1 CURSOR FOR 
        SELECT Col1, Col2 FROM INSERTED                     

        --do the job
        OPEN Cursor1
        FETCH NEXT FROM Cursor1 INTO @Col1, @Col2

        WHILE @@FETCH_STATUS = 0
        BEGIN
            IF ...something...
                    EXEC myProc1 @param1 = @Col1, @Param2 = @Col2
            ELSE
            IF ...something else...
                    EXEC myProc2 @param1 = @Col1, @Param2 = @Col2

            FETCH NEXT FROM Cursor1 INTO @Col1, @Col2                               
        END
    END TRY
    BEGIN CATCH
        --do what you have to
    END CATCH

    BEGIN TRY
        --clean it up               
        CLOSE Cursor1
        DEALLOCATE Cursor1                                  
    END TRY
    BEGIN CATCH
        --do nothing
    END CATCH
END

トリガー内のカーソルが良いアイデアであるかどうかは別の問題です...

15
gbn

CURSOR_STATUS()関数を使用できます。

if CURSOR_STATUS('global','cursor_name') >= 0 
begin
 close cursor_name
  deallocate cursor_name 
end

referencehttp://msdn.Microsoft.com/en-us/library/ms177609.aspx

40
user201667

トリガーでカーソルを使用することは絶対にしないでください。代わりに、正しいセットベースのコードを記述してください。誰かが100,000の新しいレコードのテーブルにデータをインポートした場合、テーブルを何時間もロックして、データベースを悲鳴を上げて停止させます。トリガーでカーソルを使用することは非常に悪い習慣です。

1
HLGEM

10年後、私はこの特定の質問にいくつかの情報を追加する必要があると思います。

あなたの問題には2つの主要な解決策があります。まず、LOCALカーソル宣言を使用します。

DECLARE --Operation
    Cursor1 -- Name
CURSOR -- Type
    LOCAL READ_ONLY FORWARD_ONLY -- Modifiers
FOR -- Specify Iterations
SELECT Col1, Col2 FROM INSERTED;

これにより、他のアクションがこのカーソルを呼び出していない場合、特定のカーソルがサーバーのグローバルコンテキストではなく、アクティブなセッションのみに制限されます。原則として同様に、カーソル変数を使用します。これは次のようになります。

DECLARE @Cursor1 CURSOR;
SET @Cursor1 = CURSOR LOCAL READ_ONLY FORWARD_ONLY FOR SELECT Col1, Col2 FROM INSERTED;

カーソル変数を使用する場合、前の例のように特定のセッション内にあるようにスコープを管理することに加えて、SET構文を使用していつでもカーソル変数を上書きできます。カーソルコンテキストを上書きすることにより、過去の参照の割り当てを効果的に解除できます。とは言うものの、これらのアプローチはどちらも、カーソルのステータスを現在の接続のアクティビティにリンクすることによって、本来の意図を達成します。

アプリのコンテキストが接続プールを使用している場合、これによりロックが長引く可能性があります。その場合、次のようにTry-Catchパターンを使用する必要があります。

CREATE TRIGGER trigger1
   ON [dbo].[table1] 
   AFTER UPDATE
AS 
BEGIN               
    --declare some vars
    DECLARE @Col1 SMALLINT;
    DECLARE @Col2 TINYINT;

    --declare cursor        
    DECLARE 
        Cursor1 
    CURSOR 
        LOCAL READ_ONLY FORWARD_ONLY 
    FOR 
        SELECT 
            Col1, 
            Col2 
        FROM 
            INSERTED;

    --do the job
    OPEN Cursor1;

    BEGIN TRY

        FETCH 
            NEXT 
        FROM 
            Cursor1 
        INTO 
            @Col1, 
            @Col2;

        WHILE @@FETCH_STATUS = 0
            BEGIN
                IF -- my condition
                    EXEC myProc1 @param1 = @Col1, @Param2 = @Col2;
                ELSE IF -- additional condition
                    EXEC myProc2 @param1 = @Col1, @Param2 = @Col2;

                FETCH 
                    NEXT 
                FROM 
                    Cursor1 
                INTO 
                    @Col1, 
                    @Col2;
            END;
    END TRY

    BEGIN CATCH
        -- Error Handling
    END CATCH

    --clean it up       
    CLOSE Cursor1;
    DEALLOCATE Cursor1;
END;

このようにパターンを使用すると、コードの重複が減ったり、カーソルの状態を確認したりする必要があります。基本的に、カーソルの初期化は、openステートメントと同様に安全である必要があります。カーソルが開いたら、常に閉じてセッションから割り当てを解除する必要があります。これは、カーソルが開いていることを前提とした安全なアクションである必要があります(これは、常に安全な操作である必要があります)。そのため、それらをTry-Catchの範囲外に残すことは、Catchブロックの後の最後ですべてをきちんと閉じることができることを意味します。

サンプルコードはセット内のレコード間を前後にスクロールしなかったため、カーソルのREAD_ONLY属性とFORWARD_ONLYを指定したことは言及する価値があります。これらのプロシージャで基になる行を変更する場合は、誤って無限ループが発生しないように、STATICカーソルを使用することをお勧めします。 INSERTEDテーブルを使用してカーソルコンテキストを管理しているので、これは問題にはなりませんが、他の潜在的なユースケースについては言及する価値があります。

SQL Serverのカーソルについて詳しく知りたい場合は、この件について このブログ投稿 を読むことを強くお勧めします。彼は、カーソルのさまざまな修飾子とその効果について詳しく説明しています。データベースエンジン内にあります。

0
Solonotix