45

次のトリガーを検討してください。

ALTER TRIGGER myTrigger 
   ON someTable 
   AFTER INSERT
AS BEGIN
  DELETE FROM someTable
         WHERE ISNUMERIC(someField) = 1
END

テーブル someTable があり、人々が不正なレコードを挿入するのを防ごうとしています。この質問の目的のために、不良レコードにはすべて数値のフィールド「someField」があります。

もちろん、これを行う正しい方法はトリガーを使用することではありませんが、私はソース コードを制御していません... SQL データベースだけです。そのため、不良行の挿入を実際に防ぐことはできませんが、すぐに削除することはできます。これは、私のニーズには十分です。

トリガーは機能しますが、問題が 1 つあります...トリガーが起動すると、挿入されたばかりの不良レコードが削除されないようです...古い不良レコードは削除されますが、挿入されたばかりの不良レコードは削除されません。そのため、他の誰かが来て別の INSERT を実行するまで削除されない不良レコードが 1 つ浮かんでいることがよくあります。

これはトリガーの理解に問題がありますか? トリガーの実行中に、新しく挿入された行はまだコミットされていませんか?

4

12 に答える 12

48

Insertedトリガーは変更されたデータ (または)を変更できませんDeleted。そうしないと、変更によってトリガーが再度呼び出されるため、無限の再帰が発生する可能性があります。1 つのオプションは、トリガーがトランザクションをロールバックすることです。

編集:これは、SQLの標準では、挿入および削除された行をトリガーで変更できないためです。根本的な理由は、変更によって無限再帰が発生する可能性があるためです。一般的なケースでは、この評価には、相互に再帰的なカスケード内の複数のトリガーが含まれる可能性があります。そのような更新を許可するかどうかをシステムにインテリジェントに決定させることは、計算的に扱いにくく、本質的に停止問題のバリエーションです。

これに対する受け入れられた解決策は、トランザクションをロールバックすることはできますが、トリガーが変化するデータを変更することを許可しないことです。

create table Foo (
       FooID int
      ,SomeField varchar (10)
)
go

create trigger FooInsert
    on Foo after insert as
    begin
        delete inserted
         where isnumeric (SomeField) = 1
    end
go


Msg 286, Level 16, State 1, Procedure FooInsert, Line 5
The logical tables INSERTED and DELETED cannot be updated.

このようなものは、トランザクションをロールバックします。

create table Foo (
       FooID int
      ,SomeField varchar (10)
)
go

create trigger FooInsert
    on Foo for insert as
    if exists (
       select 1
         from inserted 
        where isnumeric (SomeField) = 1) begin
              rollback transaction
    end
go

insert Foo values (1, '1')

Msg 3609, Level 16, State 1, Line 1
The transaction ended in the trigger. The batch has been aborted.
于 2009-01-01T18:52:50.340 に答える
40

You can reverse the logic. Instead of deleting an invalid row after it has been inserted, write an INSTEAD OF trigger to insert only if you verify the row is valid.

CREATE TRIGGER mytrigger ON sometable
INSTEAD OF INSERT
AS BEGIN
  DECLARE @isnum TINYINT;

  SELECT @isnum = ISNUMERIC(somefield) FROM inserted;

  IF (@isnum = 1)
    INSERT INTO sometable SELECT * FROM inserted;
  ELSE
    RAISERROR('somefield must be numeric', 16, 1)
      WITH SETERROR;
END

If your application doesn't want to handle errors (as Joel says is the case in his app), then don't RAISERROR. Just make the trigger silently not do an insert that isn't valid.

I ran this on SQL Server Express 2005 and it works. Note that INSTEAD OF triggers do not cause recursion if you insert into the same table for which the trigger is defined.

于 2009-01-01T20:04:32.020 に答える
28

ビルのコードの私の修正版は次のとおりです。

CREATE TRIGGER mytrigger ON sometable
INSTEAD OF INSERT
AS BEGIN
  INSERT INTO sometable SELECT * FROM inserted WHERE ISNUMERIC(somefield) = 1 FROM inserted;
  INSERT INTO sometableRejects SELECT * FROM inserted WHERE ISNUMERIC(somefield) = 0 FROM inserted;
END

これにより、挿入は常に成功し、偽のレコードは sometableRejects にスローされ、後で処理できます。リジェクト テーブルで、int や tinyint などではなく、すべてに nvarchar フィールドを使用することが重要です。なぜなら、リジェクトされるのは、データが期待したものではないからです。

これにより、Bill のトリガーが失敗する原因となる複数レコード挿入の問題も解決されます。10 個のレコードを同時に挿入し (select-insert-into を実行した場合など)、そのうちの 1 つだけが偽物である場合、Bill のトリガーはそれらすべてに不良のフラグを立てます。これにより、任意の数の良いレコードと悪いレコードが処理されます。

私はこのトリックをデータ ウェアハウス プロジェクトで使用しました。このプロジェクトでは、挿入側のアプリケーションがビジネス ロジックが適切かどうかを判断できず、代わりにトリガーでビジネス ロジックを実行しました。パフォーマンスには本当に厄介ですが、挿入を失敗させられない場合は機能します。

于 2009-01-22T14:20:51.970 に答える
12

CHECK 制約を使用できると思います-それはまさにそれが発明されたものです。

ALTER TABLE someTable 
ADD CONSTRAINT someField_check CHECK (ISNUMERIC(someField) = 1) ;

私の以前の答え(また、少しやり過ぎかもしれません):

間違ったデータが挿入されるのを防ぐために INSTEAD OF トリガーを使用するのが正しい方法だと思います(事後的に削除するのではなく)

于 2009-01-01T18:57:46.897 に答える
7

更新:トリガーからのDELETEは、MSSql7とMSSql2008の両方で機能します。

私はリレーショナルの第一人者でも、SQL標準の専門家でもありません。ただし、受け入れられた答えとは反対に、MSSQLは再帰的トリガー評価とネストされたトリガー評価の両方を適切に処理します。他のRDBMSについては知りません。

関連するオプションは、「再帰トリガー」と「ネストされたトリガー」です。ネストされたトリガーは32レベルに制限されており、デフォルトは1です。再帰トリガーはデフォルトでオフになっており、制限の話はありませんが、率直に言って、私はそれらをオンにしたことがないので、避けられないもので何が起こるかわかりませんスタックオーバーフロー。MSSQLはあなたのspidを殺すだけだと思います(または再帰的な制限があります)。

もちろん、それは受け入れられた答えが間違った理由を持っていることを示しているだけであり、それが間違っているということではありません。ただし、INSTEAD OFトリガーの前に、挿入されたばかりの行を楽しく更新するONINSERTトリガーを作成したことを思い出します。これはすべて正常に機能し、予想どおりでした。

挿入されたばかりの行を削除する簡単なテストも機能します。

 CREATE TABLE Test ( Id int IDENTITY(1,1), Column1 varchar(10) )
 GO

 CREATE TRIGGER trTest ON Test 
 FOR INSERT 
 AS
    SET NOCOUNT ON
    DELETE FROM Test WHERE Column1 = 'ABCDEF'
 GO

 INSERT INTO Test (Column1) VALUES ('ABCDEF')
 --SCOPE_IDENTITY() should be the same, but doesn't exist in SQL 7
 PRINT @@IDENTITY --Will print 1. Run it again, and it'll print 2, 3, etc.
 GO

 SELECT * FROM Test --No rows
 GO

ここで何か他のことが起こっています。

于 2009-01-24T06:08:53.117 に答える
4

CREATE TRIGGERドキュメントから:

削除および挿入は、論理 (概念) テーブルです。これらは、トリガーが定義されている表、つまりユーザー・アクションが試行される表と構造的に類似しており、ユーザー・アクションによって変更される可能性がある行の古い値または新しい値を保持します。たとえば、削除されたテーブルのすべての値を取得するには、次を使用します。SELECT * FROM deleted

そのため、少なくとも新しいデータを確認する方法が得られます。

ただし、通常のテーブルをクエリするときに挿入されたデータが表示されないことを指定するドキュメントには何も表示されません...

于 2009-01-01T19:17:31.177 に答える
3

私はこの参照を見つけました:

create trigger myTrigger
on SomeTable
for insert 
as 
if (select count(*) 
    from SomeTable, inserted 
    where IsNumeric(SomeField) = 1) <> 0
/* Cancel the insert and print a message.*/
  begin
    rollback transaction 
    print "You can't do that!"  
  end  
/* Otherwise, allow it. */
else
  print "Added successfully."

私はそれをテストしていませんが、論理的には、挿入されたデータを削除するのではなく、挿入を完全に防止するのではなく、挿入を元に戻す必要がないように見えます。パフォーマンスが向上するため、最終的にはより高い負荷をより簡単に処理できるようになります。

編集:もちろん、そうでなければ有効なトランザクション内で挿入発生した場合、トランザクション全体がロールバックされる可能性があるため、そのシナリオを考慮して、無効なデータ行の挿入が構成されるかどうかを判断する必要があります完全に無効なトランザクション...

于 2009-01-01T19:08:47.997 に答える
0

MS-SQL には、再帰的なトリガーの起動を防ぐ設定があります。これは、sp_configure ストアド プロシージャを介して構成され、再帰的またはネストされたトリガーをオンまたはオフにすることができます。

この場合、再帰トリガーをオフにすると、挿入されたテーブルから主キーを介してレコードをリンクし、レコードに変更を加えることができます。

質問の特定のケースでは、結果としてレコードが削除され、この特定のトリガーが再起動されないため、実際には問題ではありませんが、一般的には有効なアプローチである可能性があります。この方法で楽観的並行性を実装しました。

この方法で使用できるトリガーのコードは次のようになります。

ALTER TRIGGER myTrigger
    ON someTable
    AFTER INSERT
AS BEGIN
DELETE FROM someTable
    INNER JOIN inserted on inserted.primarykey = someTable.primarykey
    WHERE ISNUMERIC(inserted.someField) = 1
END
于 2009-05-18T02:36:13.073 に答える
0

上で概説した手法は、オプションをかなりよく説明しています。しかし、ユーザーは何を見ているのでしょうか? あなたとソフトウェアの責任者との間のこのような基本的な対立が、ユーザーとの混乱と敵対に終わらないわけがありません。

他の人は、あなたが行った変更を問題をエスカレートさせるものとして簡単に見ることができるため、行き詰まりから抜け出すためにできる限りのことをします。

編集:

私は最初の「削除取り消し」を記録し、この質問が最初に表示されたときに上記を投稿したことを認めます。もちろん、それがジョエル・スポルスキーのものだと知ったとき、私はびっくりしました。しかし、どこか近くに着陸したようです。投票は必要ありませんが、記録に残します。

IME、トリガーは、ビジネス ルールの領域外のきめの細かい整合性制約以外の場合、正しい答えになることはめったにありません。

于 2009-01-01T19:20:52.497 に答える
0

INSERT が有効である可能性はありますが、その後、無効であるがトリガーを起動しない別の UPDATE が行われる可能性はありますか?

于 2009-01-05T16:57:58.467 に答える