web-dev-qa-db-ja.com

削除をロールバックするFor Deleteトリガー内でレイラーを発生させることは可能ですか?

この受け入れられた answer の最後に、「For Delete」トリガー内でエラーが発生した場合、暗黙のトランザクションの一部であるため削除アクションがロールバックされるという主張があります。

ただし、次の例は、エラーが発生しても削除された行が削除されたままであることを示しています。

create table T1 ( i1 int  );
go

create trigger T1_ForDelete on T1
for delete
as
raiserror('Raised 16', 16, 1);
raiserror('Raised 18', 18, 1);
raiserror('Raised #2 16', 16, 255);
raiserror('Raised #2 18', 18, 255);
go

insert into T1 (i1) values (1);

set xact_abort on; -- makes no difference

delete from T1;

出力(順序変更):

Msg 50000, Level 16, State 1, Procedure T1_ForDelete, Line 4
Raised 16
Msg 50000, Level 16, State 255, Procedure T1_ForDelete, Line 6
Raised #2 16

(1 row(s) affected)
Msg 50000, Level 18, State 1, Procedure T1_ForDelete, Line 5
Raised 18
Msg 50000, Level 18, State 255, Procedure T1_ForDelete, Line 7
Raised #2 18

その後

select * from T1; -- Returns no records

これは予想される動作ですか、それともエラー(たとえば、さまざまな重大度/状態)で削除されないようにする方法はありますか?

「カスケードの削除」が採用されているため、「代わりに削除」を使用できません。 Create Trigger(MSDN)、search "For INSTEAD OF"

3
crokusek

上記でコーディングしたとおりに機能させるには、RAISERRORの後にロールバックするか、コメントに正しく記述されているように、TRY/CATCHでラップする必要があります。

それ自体では、実際のステートメントまたはバッチ終了エラーと同じようには扱われません。たとえば、これは期待どおりに機能します。

create table T1 ( i1 int  );
create table t2 (i2 int primary key);
go

create trigger T1_ForDelete on T1
for delete
as
    insert into t2 (i2)
    SELECT i1 
    FROM DELETED
go

insert into T1 (i1) values (1);
insert into t2 (i2) values (1);

delete from T1;
go

SELECT * from t1

上記のコードでは、トリガーがエラーになり、削除(外部)トランザクションがロールバックされます。 RAISERROR自体は、実際には制御を転送したり、メッセージをスタックに送信したりするだけです(いわば)。

BOLはこの動作を説明しています。

TRYブロックで重大度が11以上のRAISERRORが実行されると、関連するCATCHブロックに制御が移ります。 RAISERRORを実行すると、エラーが呼び出し元に返されます。

TRYブロックの範囲外。

TRYブロックの重大度が10以下。

データベース接続を終了する重大度20以上。

したがって、TRYブロックのスコープ外のRAISERRORは、呼び出し元にエラーを返すだけであり、定義した重大度に関係なく、ステートメント終了エラーとして扱われません。

4
Code Magician