次のようなトリガーがあるとしましょう。
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ブロックを使用しますか?
はい、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
トリガー内のカーソルが良いアイデアであるかどうかは別の問題です...
CURSOR_STATUS()関数を使用できます。
if CURSOR_STATUS('global','cursor_name') >= 0
begin
close cursor_name
deallocate cursor_name
end
reference: http://msdn.Microsoft.com/en-us/library/ms177609.aspx
トリガーでカーソルを使用することは絶対にしないでください。代わりに、正しいセットベースのコードを記述してください。誰かが100,000の新しいレコードのテーブルにデータをインポートした場合、テーブルを何時間もロックして、データベースを悲鳴を上げて停止させます。トリガーでカーソルを使用することは非常に悪い習慣です。
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のカーソルについて詳しく知りたい場合は、この件について このブログ投稿 を読むことを強くお勧めします。彼は、カーソルのさまざまな修飾子とその効果について詳しく説明しています。データベースエンジン内にあります。