web-dev-qa-db-ja.com

SQL Server-トリガーの挿入後-同じテーブルの別の列を更新します

私はこのデータベーストリガーを持っています:

CREATE TRIGGER setDescToUpper
ON part_numbers
 AFTER INSERT,UPDATE
AS
DECLARE @PnumPkid int, @PDesc nvarchar(128)

SET @PnumPkid = (SELECT pnum_pkid FROM inserted)
SET @PDesc = (SELECT UPPER(part_description) FROM inserted)

UPDATE part_numbers set part_description_upper = @PDesc WHERE pnum_pkid=@PnumPkid

GO

これは悪い考えですか?それは同じテーブルの列を更新することです。挿入と更新の両方で起動するようにします。

うまくいきます。循環的な状況が怖いだけです。トリガー内の更新は、トリガーを何度も何度も起動します。 それは起こりますか?

大文字の事を軽視しないでください。クレイジーな状況。

24
Lance Perry

現在DBに設定されているトリガーの再帰レベルに依存します。

これを行う場合:

SP_CONFIGURE 'nested_triggers',0
GO
RECONFIGURE
GO

またはこれ:

ALTER DATABASE db_name
SET RECURSIVE_TRIGGERS OFF

上記のトリガーは再び呼び出されず、安全です(何らかのデッドロックに陥らない限り、それは可能かもしれませんが、多分間違っています)。

それでも、私はしないでくださいこれは良い考えだと思います。より良いオプションは、 INSTEAD OFトリガー を使用することです。そうすれば、DBで最初の(手動の)更新を実行することを回避できます。トリガー内で定義されたもののみが実行されます。

INSTEAD OF INSERTトリガーは次のようになります。

CREATE TRIGGER setDescToUpper ON part_numbers
INSTEAD OF INSERT
AS
BEGIN
    INSERT INTO part_numbers (
        colA,
        colB,
        part_description
    ) SELECT
        colA,
        colB,
        UPPER(part_description)
    ) FROM
        INSERTED
END
GO

これにより、part_descriptionフィールドに明示的なUPPER呼び出しが適用され、これによって元のINSERTステートメントが自動的に「置換」されます。

INSTEAD OF UPDATEトリガーも同様です(1つのトリガーを作成し、それらを分離しておくことはお勧めしません)。

また、これは@Martinコメントに対処します。複数行の挿入/更新に対して機能します(この例は機能しません)。

39
rsenna

別のオプションは、更新ステートメントをIFステートメントで囲み、 TRIGGER_NESTLEVEL() を呼び出して、2回目に実行される更新を制限することです。

CREATE TRIGGER Table_A_Update ON Table_A AFTER UPDATE 
AS
IF ((SELECT TRIGGER_NESTLEVEL()) < 2)
BEGIN
    UPDATE a
    SET Date_Column = GETDATE()
    FROM Table_A a
    JOIN inserted i ON a.ID = i.ID
END

トリガーが最初に実行されると、TRIGGER_NESTLEVELが1に設定されるため、更新ステートメントが実行されます。この更新ステートメントは、今度はTRIGGER_NESTLEVELが2に設定され、更新ステートメントが実行されないことを除いて、同じトリガーを起動します。

最初にTRIGGER_NESTLEVELをチェックし、1より大きい場合はRETURNを呼び出してトリガーを終了することもできます。

IF ((SELECT TRIGGER_NESTLEVEL()) > 1) RETURN;
15
Jorriss

代わりに計算列を使用してください。ほとんどの場合、トリガーよりも計算列を使用することをお勧めします。

UPPER関数を使用した計算列の以下の例を参照してください。

create table #temp (test varchar (10), test2 AS upper(test))
insert #temp (test)
values ('test')
select * from #temp

壊れたレコードなどのようには聞こえませんが、これは非常に重要です。複数のレコードの挿入/更新/削除で正しく機能しないトリガーを作成しないでください。遅かれ早かれこれらのいずれかが発生し、トリガーがデータ整合性の問題を引き起こすので、これは非常に貧弱なプラクティスです。これは、誰かが混乱を発見するまで長い時間を要する可能性があり、それまでにデータを正しく修正することが不可能な場合がよくあります。

6
HLGEM

うん...最初の挿入で値を設定できるテーブルを更新するための追加のステップを持つことは、おそらく余分な、回避可能なプロセスです。 UPPER(part_description)値を使用して、part_descriptionをpart_description_upper列に実際に挿入できる元の挿入ステートメントにアクセスできますか?

考えた後、おそらくあなたはおそらくそうするだろうので、あなたはおそらくアクセス権を持っていないので、同様にいくつかのオプションも与えるべきです...

1)このpart_description_upper列の必要性に依存します。「表示」のためだけに、返されたpart_description値と「ToUpper()」を使用できます(プログラミング言語によって異なります)。

2)「リアルタイム」処理を回避したい場合は、SQLジョブを作成して、トラフィックの少ない期間に1日1回値を確認し、現在設定されていない列のUPPER part_description値にその列を更新します。

3)トリガーを使用します(他の人が述べたように再帰に注意してください)...

HTH

デイブ

1
Dave

はい、再帰トリガーの設定をオフにしない限り、トリガーを再帰的に呼び出します。

ALTER DATABASE db_name SET RECURSIVE_TRIGGERS OFF 

MSDNでは、Recursive Triggersという見出しの下の http://msdn.Microsoft.com/en-us/library/aa258254(SQL.80).aspx で動作の説明が適切に行われています。

1

何もする必要がない場合は、トリガーを終了する方が安全です。ネストされたレベルを確認するか、RECURSIVEをオフにしてデータベースを変更すると、問題が発生しやすくなります。

MS SQLは、トリガーで特定の列が更新されたかどうかを確認する簡単な方法を提供します。 UPDATE()メソッドを使用して、UPDATE(part_description_upper)などの特定の列が更新されているかどうかを確認します。

IF UPDATE(part_description_upper)
  return
1
Visual Micro