SQL Server“挿入後”挿入されたばかりの行がトリガーに表示されない
-
03-07-2019 - |
質問
このトリガーを検討してください:
ALTER TRIGGER myTrigger
ON someTable
AFTER INSERT
AS BEGIN
DELETE FROM someTable
WHERE ISNUMERIC(someField) = 1
END
テーブルsomeTableがあり、人々が悪いレコードを挿入しないようにしています。この質問の目的のために、不良レコードにはフィールド「someField」があります。それはすべて数値です。
もちろん、これを行う正しい方法はトリガーを使用することではありませんが、ソースコードを制御するのではなく、SQLデータベースだけを制御します。したがって、実際には不良な行の挿入を防ぐことはできませんが、すぐに削除できます。これは私のニーズに十分です。
トリガーは機能しますが、1つの問題があります...起動すると、挿入されたばかりの不良レコードは削除されないようです...古い不良レコードは削除されますが、挿入されたばかりの不良レコードは削除されません。そのため、他の誰かが来て別のINSERTを実行するまで削除されない、1つの不良レコードが頻繁に存在します。
これはトリガーの理解における問題ですか?トリガーの実行中に、新しく挿入された行はまだコミットされていませんか?
解決
トリガーは変更されたデータを変更できません( 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.
他のヒント
論理を逆にすることができます。無効な行を挿入した後に削除する代わりに、 INSTEAD OF
トリガーを記述して、行が有効であることを確認したらのみを挿入します。
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
アプリケーションがエラーを処理したくない場合(Joelが彼のアプリの場合)、 RAISERROR
を実行しないでください。トリガーを黙って有効にしない 挿入を行ってください。
SQL Server Express 2005でこれを実行しましたが、動作します。トリガーが定義されているのと同じテーブルに挿入した場合、 INSTEAD OF
トリガーは再帰を引き起こしません ことに注意してください。
Billのコードの修正版は次のとおりです。
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のトリガーはすべてのレコードを不良としてフラグ付けします。これは、任意の数の良いレコードと悪いレコードを処理します。
データウェアハウジングプロジェクトでこのトリックを使用しました。このプロジェクトでは、挿入するアプリケーションはビジネスロジックが適切かどうか判断できず、代わりにトリガーでビジネスロジックを実行しました。パフォーマンスは本当に厄介ですが、挿入を失敗させない場合は機能します。
CHECK制約を使用できると思います。まさにそれが発明されたものです。
ALTER TABLE someTable
ADD CONSTRAINT someField_check CHECK (ISNUMERIC(someField) = 1) ;
私の以前の回答(これも少しやり過ぎかもしれません):
正しい方法は、INSTEAD OFトリガーを使用して、間違ったデータが挿入されないようにすることです(事後的に削除するのではなく)
更新:トリガーからのDELETEは、MSSql 7とMSSql 2008の両方で機能します。
私はリレーショナルの第一人者でも、SQL標準でもありません。ただし、受け入れられた答えに反して、MSSQLはr 再帰的およびネストされたトリガー評価。他のRDBMSについては知りません。
関連するオプションは、「再帰トリガー」および「ネストされたトリガー」です。ネストされたトリガーは32レベルに制限され、デフォルトは1です。再帰トリガーはデフォルトでオフになっており、制限についての話はありません。スタックオーバーフロー。 MSSQLはあなたのspidを殺すだけだと思われます(または再帰的な制限があります)。
もちろん、これは受け入れられた答えが間違っている理由であることを示しているだけであり、間違っているわけではありません。ただし、INSTEAD OFトリガーの前に、挿入された行を陽気に更新するON INSERTトリガーを作成したことを思い出します。これはすべて正常に機能し、予想通りでした。
挿入されたばかりの行を削除する簡単なテストも機能します:
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
ここで何か他のことが起こっています。
トリガーの作成のドキュメント:
deleted および inserted は論理(概念)テーブルです。彼らです 上の表と構造的に類似 トリガーが定義されている、つまり ユーザーアクションが存在するテーブル 試行され、古い値を保持するか、 可能性のある行の新しい値 ユーザーのアクションによって変更されました。にとって 例では、のすべての値を取得します 削除されたテーブル、使用:
SELECT * FROM deleted
少なくとも、新しいデータを見る方法を提供します。
ただし、通常のテーブルを照会するときに挿入されたデータが表示されないことを指定するドキュメントには何も表示されません...
この参照が見つかりました:
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."
テストしていませんが、論理的には、挿入されたデータを削除するのではなく、挿入したデータを削除する必要があります。挿入を完全に防ぐため、挿入を取り消す必要はありません。パフォーマンスが向上するため、最終的にはより高い負荷をより簡単に処理する必要があります。
編集:もちろん、挿入が他の有効なトランザクション内で発生した場合、woleトランザクションをロールバックできる可能性があるので、そのシナリオを考慮して決定する必要があります 無効なデータ行の挿入が完全に無効なトランザクションを構成する場合...
INSERTは有効ですが、後で別のUPDATEが実行されますが、それは無効ですが、トリガーを起動しませんか?
上記で概説したテクニックは、オプションをかなりよく説明しています。しかし、ユーザーは何を見ていますか?あなたとソフトウェアの責任者とのこのような基本的な対立が、ユーザーとの混乱や敵対関係に陥ることができないことを想像することはできません。
行き詰まりから抜け出すために他の方法を見つけるために、できる限りのことをします。他の人は、あなたが行った変更を問題のエスカレーションとして簡単に見ることができるからです。
編集:
最初の&quot; undelete&quot;を獲得します。この質問が最初に現れたときに上記を投稿することを認めます。私はもちろんジョエル・スポスキーからだと知ったとき、私はニワトリを外した。しかし、どこか近くに着陸したようです。投票は必要ありませんが、記録に載せます。
IME、トリガーは、ビジネスルールの領域外のきめの細かい整合性制約以外の正しい答えになることはめったにありません。
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
&quot;トリガー&quot; 「トリガー」ということをしているすることを想定していません。 Sql Server Agentを簡単に実行できます
DELETE FROM someTable
WHERE ISNUMERIC(someField) = 1
1秒ごと。プログラミング中にテーブルにエラーが挿入されるのを防ぐために、素敵な小さなSPを作成してみてください。 SPの良い点の1つは、パラメーターがタイプセーフであることです。
私はこの質問に出くわして、挿入ステートメント中のイベントのシーケンスに関する詳細を探しました&amp;引き金。最終的にいくつかの簡単なテストをコーディングして、SQL 2016(EXPRESS)の動作を確認しました。他のユーザーが同様の情報を検索するのに役立つ可能性があるため、共有することが適切だと考えました。
テストに基づいて、「挿入」からデータを選択することができます。テーブルを使用して、挿入されたデータ自体を更新します。そして、興味深いことに、挿入されたデータは、最終的な結果が表示されるポイントでトリガーが完了するまで、他のクエリには表示されません(少なくともテストできる限り)。再帰的なトリガーなどについてこれをテストしませんでした(ネストされたトリガーは、テーブルに挿入されたデータの完全な可視性を期待しますが、それは単なる推測です)。
たとえば、テーブル&quot; table&quot;があるとします。整数フィールド&quot; field&quot;および主キーフィールド&quot; pk&quot;挿入トリガーの次のコード:
select @value=field,@pk=pk from inserted
update table set field=@value+1 where pk=@pk
waitfor delay '00:00:15'
&quot; field&quot;に値1の行を挿入すると、行は値2になります。さらに、SSMSで別のウィンドウを開いて、次のことを試みます。 pk = @pkの表から*を選択
@pkは最初に挿入したプライマリキーで、クエリは15秒が経過するまで空であり、更新された値(field = 2)が表示されます。
トリガーの実行中に他のクエリから見えるデータに興味がありました(明らかに新しいデータはありません)。削除も追加してテストしました:
select @value=field,@pk=pk from inserted
update table set field=@value+1 where pk=@pk
delete from table where pk=@pk
waitfor delay '00:00:15'
繰り返しますが、挿入の実行には15秒かかりました。別のセッションで実行されているクエリは、挿入+トリガーの実行中または実行後に新しいデータを示しませんでした(データが挿入されていないように見えても、IDが増加することを期待します)。