web-dev-qa-db-ja.com

再帰なしのトリガーで同じ行の列を更新する方法は?

SQL Server 2012。

テーブルのレコードが変更されるたびに、現在の日時で列[LastUpdated]を更新する必要があります。現在私は持っています:

CREATE TRIGGER Trig_LastUpdated ON Contact AFTER UPDATE
AS
SET NOCOUNT ON

UPDATE ct
SET LastUpdated = GETDATE()
FROM Contact ct
INNER JOIN Inserted i
    ON ct.IDContact = i.IDContact

しかし、これは再帰的であり、私はそれを望んでいません。デッドロックやその他の奇妙さを引き起こします。再帰トリガーをグローバルにオフにすることはできません。 INSTEAD OFトリガーが非再帰的であることがわかりましたが、そうした場合、挿入された他のすべての列をチェックして、更新されているかどうかを確認する必要がありますか、それともSQL Serverがそれを処理しますか?これを行う最良の方法は何ですか?

5
Zaphodb2002

@ypercubeᵀᴹがコメントで指摘したように、 SQL Server 2008 R2テーブルに「最終更新」列を追加するにはどうすればよいですか? の答えがあります。列が更新されているかどうかを確認すると、ロジックがスキップされ、期待どおりに機能します。ユーザーが列を手動で更新すると、更新が取得され、更新しない場合は再帰が発生しません。何らかの理由でアップデートを完全に停止すると思っていましたが、明らかにそうではありません。

1
Zaphodb2002

再帰トリガーを無効にできない場合、次善の策は次のとおりです。

  1. トリガーに TRIGGER_NESTLEVEL 関数を使用して、トリガーの深さのレベルを検出させます。スタックの最初のトリガー実行でない場合は、これをトリガーの最初に使用して単に終了します。以下に沿ったもの:

    _IF (TRIGGER_NESTLEVEL() > 1)
    BEGIN
      -- Uncomment the following PRINT line for debugging
      -- PRINT 'Exiting from recursive call to: ' + ISNULL(OBJECT_NAME(@@PROCID), '');
      RETURN;
    END;
    _

    これには、別のトリガーによって行われる最初の挿入によってどのように影響を受けるかを確認するための少しのテストが必要になります(これが問題になる場合がありますが、問題にならない場合があります)。別のトリガーから呼び出されたときに期待どおりに機能しない場合は、いくつかのパラメーターをこの関数に設定してみてください。詳細については、ドキュメント(上記にリンク)を参照してください。

  2. SET CONTEXT_INFO を使用して、セッションベースの「コンテキスト情報」にフラグを設定します。コンテキスト情報は、セッションレベルで存在するVARBINARY(128)値であり、上書きされるまで、またはセッションが終了するまでその値を保持します。値は、 CONTEXT_INFO 関数を使用するか、_context_info_列を次のいずれかのDMVから選択して取得できます: sys.dm_exec_requests および sys .dm_exec_sessions

    トリガーの先頭に次のものを配置できます。

    _IF (CONTEXT_INFO() = 0x01)
    BEGIN
      -- Uncomment the following PRINT line for debugging
      --PRINT 'Exiting from recursive call to: ' + ISNULL(OBJECT_NAME(@@PROCID), '');
      RETURN;
    END;
    ELSE
    BEGIN
      -- Uncomment the following PRINT line for debugging
      --PRINT 'Initial call to: ' + ISNULL(OBJECT_NAME(@@PROCID), '');
      SET CONTEXT_INFO 0x01;
    END;
    _

    他の理由ですでにコンテキスト情報を使用している場合、このオプションはあまり機能しません。ただし、SQL Server 2016を使用しているユーザーはだれでも、セッションベースの新しいキーと値のペアのセットである SESSION_CONTEXT を利用できます。

IF NOT UPDATE(LastUpdated)関数は、列がSET句に含まれているかどうかのみを通知できるため、UPDATE(column_name)を使用するよりもこれらの方法のいずれかが信頼性が高くなります。値が変更されたかどうか、または "現在の" GETDATE()値に変更されたかどうかは、期待/希望どおりにわかりません。つまり、次のステートメントはすべて、トリガーの望ましい効果をバイパスします(つまり、LastUpdated列に実際の変更日時が含まれていることを確認します)。

_UPDATE ct
SET    ct.LastUpdated = ct.LastUpdated
FROM   Contact ct
WHERE  ...

UPDATE ct
SET    ct.LastUpdated = '1900-04-01`
FROM   Contact ct
WHERE  ...

UPDATE ct
SET    ct.LastUpdated = 1
FROM   Contact ct
WHERE  ...

UPDATE ct
SET    ct.LastUpdated = GETDATE() + 90
FROM   Contact ct
WHERE  ...
_

最も安全な方法は、おそらくTRIGGER_NESTLEVEL()(オプション1)を使用し、この特定のトリガーのみをチェックするためのパラメーターを渡すことです。これにより、別のトリガーからのINSERTによって呼び出されても悪影響はありません。 :

_TRIGGER_NESTLEVEL( @@PROCID , 'AFTER' , 'DML' )
_
3
Solomon Rutzky