次の方法で共有


入れ子になったトリガの使用

あるトリガが別のトリガを起動する操作を実行するときは、DML トリガと DDL トリガの両方が入れ子になります。このような操作では、他のトリガを順次開始できます。DML トリガと DDL トリガは、32 レベルまで入れ子にできます。Transact-SQL トリガからマネージ コードへの参照は、32 レベルの入れ子制限の 1 レベルとしてカウントされます。マネージ コード内から呼び出されたメソッドは、この制限としてはカウントされません。

nested triggers サーバー構成オプションにより、AFTER トリガを入れ子にできるかどうかを制御できます。INSTEAD OF トリガは、このサーバー オプションの設定とは無関係に入れ子にできます。INSTEAD OF トリガにできるのは DML トリガだけです。

注意注意

SQL Server 2000 では、INSTEAD OF トリガ内で入れ子になった AFTER トリガは、nested triggers サーバー構成オプションがオフになっていれば起動されません。SQL Server 2005 以降では、INSTEAD OF トリガ内で入れ子になっている最初の AFTER トリガは、nested triggers サーバー構成オプションが 0 に設定されていても起動されます。ただし、この設定では、後続の AFTER トリガは起動されません。アプリケーションに入れ子になったトリガがないかどうかを調査し、nested triggers サーバー構成オプションが 0 に設定されている場合の動作に関して、アプリケーションがビジネス ルールに従っているかどうかを判断し、その後、適切な変更を加えることをお勧めします。

トリガを入れ子にできる場合に、トリガのチェーンのどれかが無限ループを開始すると、入れ子階層の上限を超えることになり、トリガは終了します。

入れ子になったトリガを使用して、前のトリガの影響を受けた行のバックアップ コピーを保存するなど、システムの運用上有益な機能を実行することができます。たとえば、delcascadetrig トリガが削除した PurchaseOrderDetail 行のバックアップ コピーを保存するトリガを PurchaseOrderDetail に作成することができます。delcascadetrig トリガが有効な場合、PurchaseOrderHeader から PurchaseOrderID 1965 が削除されると、PurchaseOrderDetail から対応する行が削除されます。このデータを保存するには、PurchaseOrderDetail に DELETE トリガを作成します。このトリガでは削除されたデータが、別に作成されたテーブル del_save に保存されます。次に例を示します。

CREATE TRIGGER savedel
   ON Purchasing.PurchaseOrderDetail
FOR DELETE
AS
   INSERT del_save
   SELECT * FROM deleted

入れ子の順序に依存するトリガを使用することはお勧めしません。個別のトリガを使用し、順番にデータ修正を行ってください。

注意注意

トリガはトランザクション内で実行されるので、入れ子になったトリガのいずれかのレベルで障害が発生すると、トランザクション全体が取り消され、すべてのデータ修正がロールバックされます。どこで障害が発生したかを判断できるように、トリガに PRINT ステートメントを含めてください。

再帰トリガ

RECURSIVE_TRIGGERS データベース オプションが ON になっている場合を除いて、AFTER トリガが自分自身を再帰呼び出しすることはありません。

再帰には、次の 2 種類があります。

  • 直接再帰

    起動されたトリガによる処理が、同じトリガを再び起動する場合にこの再帰が発生します。たとえば、アプリケーションで T3 テーブルが更新され、これにより Trig3 トリガが起動されたとします。Trig3 がテーブル T3 を更新するトリガだとすると、テーブルが再度更新され、Trig3 が再び起動されることになります。

    SQL Server 2008 では、別の種類 (AFTER または INSTEAD OF) のトリガが呼び出された後で、同じトリガが呼び出されても、直接再帰が発生します。つまり、同じ INSTEAD OF トリガが 2 回呼び出されると、その間に AFTER トリガが 1 回以上呼び出されていたとしても、INSTEAD OF トリガの直接再帰が発生します。同様に、同じ AFTER トリガが 2 回呼び出されると、その間に INSTEAD OF トリガが 1 回以上呼び出されていたとしても、AFTER トリガの直接再帰が発生します。たとえば、アプリケーションがテーブル T4 を更新します。この更新により、INSTEAD OF トリガ Trig4 が起動します。Trig4 はテーブル T5 を更新します。この更新により、AFTER トリガ Trig5 が起動します。Trig5 がテーブル T4 を更新し、これにより INSTEAD OF トリガ Trig4 が再び起動されます。このようなイベントの連鎖は、Trig4 に対する直接再帰と見なされます。

  • 間接再帰

    起動されたトリガが実行した処理によって、同じ種類 (AFTER または INSTEAD OF) の別のトリガが起動する場合、この再帰が発生します。この 2 番目のトリガにより、最初のトリガを再度起動する操作が実行されます。つまり、ある INSTEAD OF トリガが 2 回呼び出され、その間に別の INSTEAD OF トリガが呼び出されていると、間接再帰が発生します。同様に、ある AFTER トリガが 2 回呼び出され、その間に別の AFTER トリガが呼び出されていると、間接再帰が発生します。たとえば、アプリケーションがテーブル T1 を更新します。この更新により、AFTER トリガ Trig1 が起動します。Trig1 がテーブル T2 を更新し、これにより AFTER トリガ Trig2 が起動します。次に、Trig2 がテーブル T1 を更新し、これにより AFTER トリガ Trig1 が再び起動します。

RECURSIVE_TRIGGERS データベース オプションが OFF の場合は、AFTER トリガの直接再帰呼び出しのみが回避されます。AFTER トリガの間接再帰を無効にするには、nested triggers サーバー オプションを 0 に設定します。

次の例では、再帰トリガを使用して、自己参照型リレーションシップ (トランジティブ クロージャとも呼ばれます) を解決する方法を示しています。たとえば、emp_mgr テーブルで、次のものが定義されているとします。

  • 会社内の従業員 (emp)。

  • 各従業員の管理者 (mgr)。

  • 各従業員へ報告を行う、組織構成内の従業員の総数 (NoOfReports)。

再帰的な UPDATE トリガを使用すると、新しい従業員のレコードが挿入されたときに NoOfReports 列を最新の状態に更新できます。INSERT トリガにより、その従業員の管理者のレコードの NoOfReports 列の値が更新されます。これにより組織構成の上部に向かって、その他のレコードの NoOfReports 列が再帰的に更新されます。

USE AdventureWorks;
GO
-- Turn recursive triggers ON in the database.
ALTER DATABASE AdventureWorks
   SET RECURSIVE_TRIGGERS ON;
GO
CREATE TABLE emp_mgr (
   emp char(30) PRIMARY KEY,
    mgr char(30) NULL FOREIGN KEY REFERENCES emp_mgr(emp),
    NoOfReports int DEFAULT 0
);
GO
CREATE TRIGGER emp_mgrins ON emp_mgr
FOR INSERT
AS
DECLARE @e char(30), @m char(30)
DECLARE c1 CURSOR FOR
   SELECT emp_mgr.emp
   FROM   emp_mgr, inserted
   WHERE emp_mgr.emp = inserted.mgr

OPEN c1
FETCH NEXT FROM c1 INTO @e
WHILE @@fetch_status = 0
BEGIN
   UPDATE emp_mgr
   SET emp_mgr.NoOfReports = emp_mgr.NoOfReports + 1 -- Add 1 for newly
   WHERE emp_mgr.emp = @e                            -- added employee.

   FETCH NEXT FROM c1 INTO @e
END
CLOSE c1
DEALLOCATE c1
GO
-- This recursive UPDATE trigger works assuming:
--   1. Only singleton updates on emp_mgr.
--   2. No inserts in the middle of the org tree.
CREATE TRIGGER emp_mgrupd ON emp_mgr FOR UPDATE
AS
IF UPDATE (mgr)
BEGIN
   UPDATE emp_mgr
   SET emp_mgr.NoOfReports = emp_mgr.NoOfReports + 1 -- Increment mgr's
   FROM inserted                            -- (no. of reports) by
   WHERE emp_mgr.emp = inserted.mgr         -- 1 for the new report.

   UPDATE emp_mgr
   SET emp_mgr.NoOfReports = emp_mgr.NoOfReports - 1 -- Decrement mgr's
   FROM deleted                             -- (no. of reports) by 1
   WHERE emp_mgr.emp = deleted.mgr          -- for the new report.
END
GO
-- Insert some test data rows.
INSERT emp_mgr(emp, mgr) VALUES ('Harry', NULL)
INSERT emp_mgr(emp, mgr) VALUES ('Alice', 'Harry')
INSERT emp_mgr(emp, mgr) VALUES ('Paul', 'Alice')
INSERT emp_mgr(emp, mgr) VALUES ('Joe', 'Alice')
INSERT emp_mgr(emp, mgr) VALUES ('Dave', 'Joe')
GO
SELECT * FROM emp_mgr
GO
-- Change Dave's manager from Joe to Harry
UPDATE emp_mgr SET mgr = 'Harry'
WHERE emp = 'Dave';
GO
SELECT * FROM emp_mgr;
GO

以下は、更新を行う前の状態です。

emp                            mgr                           NoOfReports
------------------------------ ----------------------------- -----------
Alice                          Harry                          2
Dave                           Joe                            0
Harry                          NULL                           1
Joe                            Alice                          1
Paul                           Alice                          0

以下は、更新を行った後の状態です。

emp                            mgr                           NoOfReports
------------------------------ ----------------------------- -----------
Alice                          Harry                          2
Dave                           Harry                          0
Harry                          NULL                           2
Joe                            Alice                          0
Paul                           Alice                          0

入れ子になったトリガのオプションを設定するには

RECURSIVE_TRIGGERS データベース オプションを設定するには