ヘッダーをスキップ
Oracle Database PL/SQL言語リファレンス
11g リリース1(11.1)
E05670-03
  目次
目次
索引
索引

戻る
戻る
 
次へ
次へ
 

9 トリガーの使用

トリガーとは、データベースに格納され、データベース内で発生した指定のイベントに応答して実行(起動)される名前付きPL/SQLユニットです。

ここでのトピック:

トリガーの概要

トリガーとは、データベースに格納され、指定のイベントに応答して起動(実行)される名前付きプログラム・ユニットです。指定のイベントは、表、ビュー、スキーマまたはデータベースのいずれかに関連付けられていて、次のいずれかになります。

トリガーは、表、ビュー、スキーマまたはデータベースに対して定義されると言われています。

ここでのトピック:

トリガーのタイプ

DMLトリガーはDML文によって起動され、DDLトリガーはDDL文によって起動され、DELETEトリガーDELETE文によって起動されます。その他の文も同様です。

INSTEAD OFトリガーは、(表ではなく)ビューに対して定義されるDMLトリガーです。 データベースでは、トリガーを起動するDML文が実行されるのではなく、INSTEAD OFトリガーが起動されます。 詳細は、「複合ビューの変更(INSTEAD OFトリガー)」を参照してください。

システム・トリガーは、スキーマまたはデータベースに対して定義されます。スキーマに対して定義されたトリガーは、そのスキーマの所有者(現行ユーザー)に関連付けられているイベントごとに起動されます。データベースに対して定義されたトリガーは、すべてのユーザーに関連付けられているイベントごとに起動されます。

単純なトリガーは、次のいずれかのタイミングで起動できます。

  • トリガーを起動する文が実行される前

  • トリガーを起動する文が実行された後

  • トリガーを起動する文の影響を受ける各行の前

  • トリガーを起動する文の影響を受ける各行の後

複合トリガーは、複数のタイミングで起動できます。複合トリガー使用すると、様々なタイミングで実装したアクションで共通データを共有するアプローチを簡単にプログラムできるようになります。 詳細は、「複合トリガー」を参照してください。

トリガーの状態

トリガーは、次の2つの状態のいずれかになります。

有効。有効になっているトリガーは、トリガーを起動する文が入力され、トリガー制限(存在する場合)がTRUEと評価された場合、トリガー本体を実行します。

無効。無効になっているトリガーは、トリガーを起動する文が入力され、トリガー制限(存在する場合)がTRUEと評価された場合でも、トリガー本体を実行しません。

デフォルトでは、トリガーは有効な状態で作成されます。無効な状態でトリガーを作成するには、CREATE TRIGGER文のDISABLE句を使用します。

トリガーのデータ・アクセス

トリガー・アクションで参照される表は、トリガーの起動時に、他のユーザーのトランザクションで使用されたSQL文によって変更中である可能性があります。 すべての場合で、トリガー内で実行されるSQL文は、スタンドアロンのSQL文で使用される共通の規則に従います。 特に、コミットされていないトランザクションで、起動されているトリガーによって読取り(問合せ)または書込み(更新)を行う必要がある値を変更した場合、起動されているトリガー本体のSQL文は、次のガイドラインに従います。

  • 問合せでは、参照される表の読取り一貫性のある現在のマテリアライズド・ビューおよび同一トランザクション内で変更されたデータが確認されます。

  • 更新では、既存のデータ・ロックが解放されるまで待機してから、処理が続行されます。

トリガーの使用

トリガーは、データベースの標準的な機能を補い、高度にカスタマイズされたデータベース管理システムを提供します。 たとえば、トリガーを使用すると、次の操作を行うことができます。

  • 導出列値の自動生成

  • 分散データベース内の異なるノード間での参照整合性の実行

  • 複雑なビジネス・ルールの実行

  • 透過的なイベント・ロギング

  • 監査機能の提供

  • 表のレプリケートでの同期の維持

  • 表へのアクセスに関する統計情報の収集

  • ビューに対してDML文が発行された場合の表データの変更

  • サブスクライブ元のアプリケーションへのデータベース・イベント、ユーザー・イベントおよびSQL文に関する情報のパブリッシュ

  • 表に対するDML操作を通常の業務時間内に発行される操作に制限

  • セキュリティ認可の実行

  • 無効なトランザクションの防止


注意:

トリガーは、プログラムで構築されるものであり、無効になりやすいため、信頼性の高いセキュリティ・メカニズムではありません。 高いセキュリティを確保するには、Oracle Database Vaultを使用してください。 詳細は、『Oracle Database Vault管理者ガイド』を参照してください。

トリガーを設計する場合のガイドライン

トリガーを設計する場合は、次のガイドラインを使用します。

トリガーの使用に必要な権限

ユーザー自身のスキーマ内にトリガーを作成するには、次の権限が必要です。

別のスキーマ内にトリガーを作成する場合、または自身のスキーマ内のトリガーから別のスキーマ内の表を参照する場合は、次の権限が必要です。

データベースにトリガーを作成するには、ADMINISTER DATABASE TRIGGER権限が必要です。この権限を後で取り消すと、トリガーは削除できますが、変更できなくなります。

トリガー本体で参照されるスキーマ・オブジェクトに対するオブジェクト権限は、トリガーの所有者に(ロールを介さずに)明示的に付与する必要があります。トリガー本体の文は、トリガーを起動する文を発行するユーザーの権限ドメインではなく、そのトリガーの所有者の権限ドメインで操作します(これは、ストアド・サブプログラムの権限モデルと類似しています)。

トリガーの作成

トリガーを作成するには、CREATE TRIGGER文を使用します。デフォルトでは、トリガーは有効な状態で作成されます。無効な状態でトリガーを作成するには、CREATE TRIGGER文のDISABLE句を使用します。 トリガーの状態の詳細は、「トリガーの概要」を参照してください。

SQL*PlusやEnterprise Managerなどの対話型ツールでCREATE TRIGGER文を使用する場合は、emp表に単純なトリガーを作成する例9-1のように、最終行にスラッシュ(/)を1つ付けます。

例9-1 CREATE TRIGGER文

CREATE OR REPLACE TRIGGER Print_salary_changes
  BEFORE DELETE OR INSERT OR UPDATE ON emp
  FOR EACH ROW
WHEN (NEW.EMPNO > 0)
DECLARE
    sal_diff number;
BEGIN
    sal_diff  := :NEW.SAL  - :OLD.SAL;
    dbms_output.put('Old salary: ' || :OLD.sal);
    dbms_output.put('  New salary: ' || :NEW.sal);
    dbms_output.put_line('  Difference ' || sal_diff);
END;
/

DML操作が表で実行されると、例9-1のトリガーが起動されます。トリガーを起動する操作の組合せは選択できます。

トリガーは、BEFOREキーワードを使用するため、表に入る前に新しい値にアクセスできます。また、簡単に修正されるエラーがある場合は、:NEW.column_nameに代入することによってその値を変更できます。トリガーは、初期変更が適用され、表が一貫性のある状態に戻った後にのみ同じ表の問合せまたは変更を実行できるため、トリガーでこれらの操作を実行する場合は、AFTERキーワードを使用します。

トリガーは、FOR EACH ROW句を使用するため、複数行の更新や削除時などに複数回実行される場合があります。操作が発生したという事実の記録のみを行い、各行のデータを調べない場合は、この句を省略します。

トリガーの作成後に、次のSQL文によって、更新される行ごとにトリガーが1回起動され、いずれの場合にも、新しい給与、古い給与およびそれらの差が出力されます。

UPDATE emp SET sal = sal + 500.00 WHERE deptno = 10;

PL/SQLブロックにエラーが存在すると、CREATE(またはCREATE OR REPLACE)文は正常に実行されません。

次の項では、例9-1を使用して、トリガーの各部分を指定する方法について説明します。 CREATE TRIGGER文の追加の例は、「トリガー・アプリケーションの例」を参照してください。

ここでのトピック:

トリガーの名前指定

トリガーの名前は、同じスキーマ内の他のトリガーに対して一意である必要があります。 トリガーの名前は、他のスキーマ・オブジェクト(表、ビュー、サブプログラムなど)に対して一意である必要はありません。たとえば、表とトリガーに同じ名前を付けることもできます(ただし、混乱を避けるために、違う名前を付けることをお薦めします)。

トリガーが起動するタイミング

トリガーは、トリガーを起動する文に基づいて起動されます。トリガーを起動する文では、次のものを指定します。

  • トリガー本体を起動するSQL文、データベース・イベントまたはDDLイベント。オプションとして、DELETEINSERTおよびUPDATEがあります。これらのオプションのうちの1つ、2つまたは3つすべてを、トリガーを起動する文の仕様部に含めることができます。

  • トリガーが定義される表、ビュー、DATABASEまたはSCHEMA


    注意:

    トリガーを起動する文には、表またはビューを1つのみ指定できます。INSTEAD OFオプションを使用する場合は、トリガーを起動する文にビューを指定する必要があります。逆に、トリガーを起動する文にビューが指定されている場合は、INSTEAD OFオプションのみを使用できます。

例9-1では、PRINT_SALARY_CHANGESトリガーは、emp表に対してDELETEINSERTまたはUPDATEのいずれかが実行された後に起動されます。次のいずれかの文によって、PRINT_SALARY_CHANGESトリガーが起動されます。

DELETE FROM emp;
INSERT INTO emp VALUES ( ... );
INSERT INTO emp SELECT ... FROM ... ;
UPDATE emp SET ... ;

インポートおよびSQL*Loaderによるトリガーの起動

INSERTトリガーは、SQL*Loaderによる通常のロード中に起動されます。(ダイレクト・ロードの場合、トリガーはロードの前に無効になります。)

IMP文のIGNOREパラメータは、インポート操作中にトリガーを起動するかどうかを決定します。

  • IGNORE=N(デフォルト)で、表がすでに存在する場合、インポートによって表は変更されず、既存のトリガーは起動されません。

  • 表が存在しない場合は、トリガーが定義される前にインポートによって表が作成されてロードされるため、この場合もトリガーは起動されません。

  • IGNORE=Yの場合は、インポートによって行が既存の表にロードされます。既存のトリガーがすべて起動され、インポートされたデータが反映されるように索引が更新されます。

列リストのUPDATEトリガーに対する影響

UPDATE文に列のリストが含まれている場合があります。トリガーを起動する文に列リストが含まれていると、トリガーは、指定された列の1つが更新される場合にのみ起動されます。トリガーを起動する文で列リストが省略されていると、トリガーは、関連付けられている表のいずれかの列が更新された場合に起動されます。INSERTまたはDELETEトリガーを起動する文には列リストを指定できません。

前述のPRINT_SALARY_CHANGESトリガーの例では、トリガーを起動する文に列リストを指定できます。次に例を示します。

... BEFORE DELETE OR INSERT OR UPDATE OF ename ON emp ...

注意:

  • INSTEAD OFトリガーを指定してUPDATEに列リストを指定することはできません。

  • UPDATE OF句に指定された列がオブジェクト列の場合は、任意のオブジェクトの属性が変更された場合にもトリガーが起動されます。

  • UPDATE OF句はコレクション列には指定できません。


トリガーが起動するタイミングの制御(BEFOREオプションおよびAFTERオプション)


注意:

このトピックの内容は、単純なトリガーにのみ適用されます。複合トリガーのオプションの詳細は、「複合トリガー」を参照してください。

CREATE TRIGGER文のBEFOREまたはAFTERオプションによって、実行中のトリガーを起動する文に対してトリガー本体が起動されるタイミングが正確に指定されます。CREATE TRIGGER文では、トリガーを起動する文の直前にBEFOREまたはAFTERオプションを指定します。たとえば、前述の例では、PRINT_SALARY_CHANGESトリガーがBEFOREトリガーです。

通常、次の結果を得るためにBEFOREまたはAFTERトリガーを使用します。

  • 行データがディスクに書き込まれる前に行を変更するために、BEFORE行トリガーを使用します。

  • 行IDを使用して取得および操作を実行するために、AFTER行トリガーを使用します。

    トリガーを起動する文によってORA-2292エラーが発生すると、AFTER行トリガーが起動されます。


    注意:

    BEFORE行トリガーを使用すると、AFTER行トリガーより少し効率が上がります。AFTER行トリガーでは、影響を受けるデータ・ブロックをトリガーのために1回読み取った後(物理読取りでなく、論理読取り)、トリガーを起動する文のために再度読み取る必要があります。BEFORE行トリガーでは、データ・ブロックを、トリガーを起動する文およびトリガーの両方に対して1回のみ読み取る必要があります。

UPDATE文またはDELETE文で同時実行中のUPDATEとの競合が検出されると、データベースによってSAVEPOINTまでの透過的ROLLBACKが実行され、更新が再開されます。この処理は、文が正常に完了するまで何回も行われる可能性があります。文が再起動されるたびに、BEFORE文トリガーが再度起動されます。セーブポイントまでのロールバックでは、トリガー内で参照されるパッケージ変数への変更は取り消されません。パッケージに、この状況を検出するためのカウンタ変数を含めてください。

トリガーの順序

リレーショナル・データベースでは、SQL文によって処理される行の順序が保証されません。したがって、行の処理順序に依存するトリガーは作成しないでください。たとえば、グローバル変数の現行の値が、行トリガーによって処理される行に依存する場合は、行トリガー内のグローバル・パッケージ変数に値を代入しないでください。また、グローバル・パッケージ変数がトリガー内で更新される場合は、これらの変数をBEFORE文トリガー内で初期化することをお薦めします。

トリガー本体内の文によって他のトリガーが起動される場合、それらのトリガーはカスケードしていると言われます。 データベースでは、一度に最大32個のトリガーをカスケードできます。トリガーを実行するたびにカーソルをオープンする必要があるため、OPEN_CURSORS初期化パラメータを使用して、カスケードするトリガーの数を制限することができます。

トリガーは、インラインで、またはサブプログラムを起動することによって一連の操作を実行できますが、同じ型の複数のトリガーを使用すると、同じ表に対するトリガーを持つアプリケーションのインストールをモジュール化できます。

後続の各トリガーは、前に起動されたトリガーによって変更された内容を参照します。各トリガーは、古い値および新しい値を参照できます。古い値は元の値で、新しい値は最後に起動されたUPDATEトリガーまたはINSERTトリガーによって設定された現行の値です。

データベースでは、別の型のトリガーの実行前に、同じ型のすべてのトリガーが実行されます。1つの表に対して同じ型のトリガーが複数あり、それらの実行順序が重要な場合は、FOLLOWS句を使用してください。 FOLLOWS句を使用しない場合、データベースでは任意の予測不可能な順序が選択されます。


参照:

トリガーの順序とFOLLOWS句の詳細は、「CREATE TRIGGER文」を参照してください。

複合ビューの変更(INSTEAD OFトリガー)


注意:

INSTEAD OFトリガーは、表ではなくビューに対してのみ定義できます。

更新可能なビューとは、基礎となる表でDMLを実行できるビューのことです。 本質的に更新可能なビューもありますが、「INSTEAD OFトリガーが必要なビュー」に示されている、1つ以上の構造体で作成されているビューは更新できません。

前述のいずれかの構造体が含まれているビューは、INSTEAD OFトリガーを使用して更新可能にできます。INSTEAD OFトリガーを使用すると、UPDATE文、INSERT文およびDELETE文では直接変更できないビューを透過的に変更できます。 これらのトリガーは、他のタイプのトリガーとは異なり、データベースで、トリガーを起動する文を実行するかわりに起動されるため、INSTEAD OFトリガーと呼ばれます。このトリガーは、対象となる操作を判断し、UPDATEINSERTまたはDELETE操作を基礎となる表に対して直接実行する必要があります。

INSTEAD OFトリガーを使用して、通常のUPDATE文、INSERT文およびDELETE文をビューに対して書き込むと、正しいアクションが実行されるようにINSTEAD OFトリガーがバックグラウンドで透過的に動作します。

INSTEAD OFトリガーは、各行に対してのみアクティブにできます。


注意:

  • INSTEAD OFオプションは、ビューに対して定義されたトリガーにのみ使用できます。

  • BEFOREオプションおよびAFTERオプションは、ビューに対して定義されたトリガーには使用できません。

  • ビューのCHECKオプションは、INSTEAD OFトリガーを使用してビューに対する挿入または更新を行う場合は実行されません。INSTEAD OFトリガー本体でチェックを実行する必要があります。


INSTEAD OFトリガーが必要なビュー

ビューの問合せに次のいずれかの構造体が含まれている場合、UPDATE文、INSERT文またはDELETE文によってビューは変更できません。

  • 集合演算子

  • DISTINCT演算子

  • 集計ファンクションまたは分析ファンクション

  • GROUP BY句、ORDER BY句、MODEL句、CONNECT BY句またはSTART WITH

  • SELECTリスト内のコレクション式

  • SELECTリスト内の副問合せ

  • WITH READ ONLYが指定された副問合せ

  • 『Oracle Database管理者ガイド』で説明されている結合(一部例外あり)

ビューに擬似列または式が含まれている場合は、擬似列または式のいずれも参照しないUPDATE文でのみビューを更新できます。

INSTEAD OFトリガーを使用すると、OCIコールを介してクライアント側でオブジェクト・ビューのインスタンスを変更できます。


参照:

『Oracle Call Interfaceプログラマーズ・ガイド』

クライアント側のオブジェクト・キャッシュ内のオブジェクト・ビューによってマテリアライズされたオブジェクトを変更して、永続ストアにフラッシュするには、オブジェクト・ビューが変更可能でないかぎり、INSTEAD OFトリガーを指定する必要があります。ただし、オブジェクトが読取り専用の場合は、トリガーを定義してそのオブジェクトを確保する必要はありません。

ネストした表のビューの列に対するトリガー

INSTEAD OFトリガーは、ネストした表のビューの列に対しても作成できます。このトリガーによって、ネストした表の要素を更新する方法が提供されます。このトリガーは、ネストした表の更新される各要素に対して起動されます。トリガー内の行の相関変数は、ネストした表の要素に対応します。このタイプのトリガーは、変更対象のネストした表を含む親行にアクセスするための追加の相関名も提供します。


注意:

このトリガーの特徴は次のとおりです。
  • ビュー内のネストした表の列に対してのみ定義できます。

  • ネストした表の要素が、TABLE句を使用して変更される場合にのみ起動されます。ビューに対してDML文が実行される場合は起動されません。


たとえば、従業員のネストした表を含む部門ビューについて考えてみます。

CREATE OR REPLACE VIEW Dept_view AS
  SELECT d.Deptno, d.Dept_type, d.Dname,
    CAST (MULTISET ( SELECT e.Empno, e.Empname, e.Salary)
      FROM emp e
        WHERE e.Deptno = d.Deptno) AS Amp_list_ Emplist
      FROM dept d;

CASTMULTISET)演算子によって、部門ごとに従業員の多重集合が作成されます。従業員のネストした表であるemplist列を変更するには、この列に対してINSTEAD OFトリガーを定義して操作を処理します。

次に、挿入トリガーの作成方法の例を示します。

CREATE OR REPLACE TRIGGER Dept_emplist_tr
  INSTEAD OF INSERT ON NESTED TABLE Emplist OF Dept_view
    REFERENCING NEW AS Employee
      PARENT AS Department
        FOR EACH ROW
BEGIN
  -- Insert on nested table translates to insert on base table:
  INSERT INTO emp VALUES (:Employee.Empno,
    :Employee.Ename,:Employee.Sal, :Department.Deptno);
END;

ネストした表にINSERTが実行されるとトリガーが起動され、emp表に正しい値が挿入されます。次に例を示します。

INSERT INTO TABLE (SELECT d.Emplist FROM Dept_view d WHERE Deptno = 10)
  VALUES (1001, 'John Glenn', 10000);

この例の:department.deptno相関変数には値10が指定されています。

例: INSTEAD OFトリガー


注意:

この例を実行するには、次のデータ構造を設定する必要がある場合があります。
CREATE TABLE Project_tab (
  Prj_level NUMBER,
  Projno    NUMBER,
  Resp_dept NUMBER);
CREATE TABLE emp (
  Empno     NUMBER NOT NULL,
  Ename     VARCHAR2(10),
  Job       VARCHAR2(9),
  Mgr       NUMBER(4),
  Hiredate  DATE,
  Sal       NUMBER(7,2),
  Comm      NUMBER(7,2),
  Deptno    NUMBER(2) NOT NULL);

CREATE TABLE dept (
  Deptno    NUMBER(2) NOT NULL,
  Dname     VARCHAR2(14),
  Loc       VARCHAR2(13),
  Mgr_no    NUMBER,
  Dept_type NUMBER);

次に、MANAGER_INFOビューに行を挿入するINSTEAD OFトリガーの例を示します。

CREATE OR REPLACE VIEW manager_info AS
  SELECT e.ename, e.empno, d.dept_type, d.deptno, p.prj_level,  p.projno
    FROM emp e, dept d, Project_tab p
      WHERE e.empno =  d.mgr_no
        AND d.deptno = p.resp_dept;

CREATE OR REPLACE TRIGGER manager_info_insert
  INSTEAD OF INSERT ON manager_info
    REFERENCING NEW AS n  -- new manager information
      FOR EACH ROW
DECLARE
  rowcnt number;
BEGIN
  SELECT COUNT(*) INTO rowcnt FROM emp WHERE empno = :n.empno;
  IF rowcnt = 0  THEN
    INSERT INTO emp (empno,ename) VALUES (:n.empno, :n.ename);
  ELSE
    UPDATE emp SET emp.ename = :n.ename WHERE emp.empno = :n.empno;
  END IF;
  SELECT COUNT(*) INTO rowcnt FROM dept WHERE deptno = :n.deptno;
  IF rowcnt = 0 THEN
    INSERT INTO dept (deptno, dept_type)
      VALUES(:n.deptno, :n.dept_type);
  ELSE
    UPDATE dept SET dept.dept_type = :n.dept_type
      WHERE dept.deptno = :n.deptno;
  END IF;
  SELECT COUNT(*) INTO rowcnt FROM Project_tab
    WHERE Project_tab.projno = :n.projno;
  IF rowcnt = 0 THEN
    INSERT INTO Project_tab (projno, prj_level)
      VALUES(:n.projno, :n.prj_level);
  ELSE
    UPDATE Project_tab SET Project_tab.prj_level = :n.prj_level
      WHERE Project_tab.projno = :n.projno;
  END IF;
END;

MANAGER_INFOビューに行を挿入するアクションでは、まず、MANAGER_INFOの導出元の実表に該当する行が存在するかどうかを確認するテストが実行されます。その後、必要に応じて、新しい行の挿入または既存の行の更新が実行されます。類似のトリガーを使用して、UPDATEおよびDELETEに適したアクションを指定できます。

トリガーの1回または複数回の起動(FOR EACH ROWオプション)


注意:

このトピックの内容は、単純なトリガーにのみ適用されます。複合トリガーのオプションの詳細は、「複合トリガー」を参照してください。

FOR EACH ROWオプションによって、トリガーが行トリガーになるか文トリガーになるかが決定されます。FOR EACH ROWを指定すると、トリガーを起動する文によって影響を受ける表の各行に対してトリガーが1回起動されます。FOR EACH ROWオプションを指定しない場合、トリガーは、該当する各文に対して1回のみ起動され、その文によって影響される各行に対して別々に起動されることはありません。

たとえば、表Emp_logが次のように作成されたとします。

CREATE TABLE Emp_log (
  Emp_id     NUMBER,
  Log_date   DATE,
  New_salary NUMBER,
  Action     VARCHAR2(20));

次のトリガーを定義します。

CREATE OR REPLACE TRIGGER Log_salary_increase
  AFTER UPDATE ON emp
    FOR EACH ROW
      WHEN (NEW.Sal > 1000)
BEGIN
  INSERT INTO Emp_log (Emp_id, Log_date, New_salary, Action)
    VALUES (:NEW.Empno, SYSDATE, :NEW.SAL, 'NEW SAL');
END;

その後、次のSQL文を入力します。

UPDATE emp SET Sal = Sal + 1000.0
   WHERE Deptno = 20;

部門20に5人の従業員がいる場合、この文を入力するとトリガーが5回起動されます。これは、5つの行が影響を受けるためです。

次のトリガーは、emp表の各UPDATEに対して1回のみ起動されます。

CREATE OR REPLACE TRIGGER Log_emp_update
  AFTER UPDATE ON emp
BEGIN
  INSERT INTO Emp_log (Log_date, Action)
    VALUES (SYSDATE, 'emp COMMISSIONS CHANGED');
END;

文レベルのトリガーは、文全体の妥当性チェックを実行する場合に有効です。

条件に基づいたトリガーの起動(WHEN句)

オプションで、WHEN句にSQLのブール式を指定して、行トリガーの定義にトリガー制限を含めることができます。


注意:

WHEN句は、文トリガーの定義に含めることはできません。

WHEN句を含めると、この句の式がトリガーの影響を受ける各行に対して評価されます。

行に対して式がTRUEと評価されると、その行のかわりにトリガー本体が実行されます。ただし、行に対して式がFALSEまたはNOT TRUEと評価された場合(NULLの場合のように不明な場合)、その行に対してトリガー本体は実行されません。WHEN句の評価は、トリガーを起動するSQL文の実行には影響しません(つまり、WHEN句の式がFALSEと評価されても、トリガーを起動する文はロールバックされません)。

たとえば、PRINT_SALARY_CHANGESトリガーでは、Empnoの新しい値が0(ゼロ)、NULLまたは負の場合、トリガー本体は実行されません。より現実的な例としては、ある列の値が他の列の値より小さいかどうかテストする場合があります。

行トリガーのWHEN句の式には相関名を指定できます。相関名については後述します。WHEN句の式はSQL式にする必要があり、この式に副問合せを含めることはできません。WHEN句では、PL/SQL式(ユーザー定義ファンクションを含む)は使用できません。


注意:

WHEN句は、INSTEAD OFトリガーには指定できません。

複合トリガー

複合トリガーは、複数のタイミングで起動できます。

ここでのトピック:

複合トリガーを使用する理由

複合トリガー使用すると、様々なタイミングで実装したアクションで共通データを共有するアプローチを簡単にプログラムできるようになります。単純なトリガーで同じ効果を得るには、補助パッケージを使用して共通の状態をモデル化する必要があります。このアプローチは、プログラムしにくく、また、トリガーを起動する文でエラーが発生し、AFTER文トリガーが起動されなかった場合にメモリー・リークが発生する可能性がありました。

複合トリガーには、オプションの宣言部および各タイミング用の部分があります(例9-2を参照してください)。これらのすべての部分から共通のPL/SQL状態にアクセスできます。共通の状態は、トリガーを起動する文でエラーが発生した場合でも、トリガーを起動する文の開始時に確立され、トリガーを起動する文の完了時に破棄されます。

例9-2 複合トリガー

SQL> CREATE OR REPLACE TRIGGER compound_trigger
  2    FOR UPDATE OF salary ON employees
  3      COMPOUND TRIGGER
  4
  5    -- Declarative part (optional)
  6    -- Variables declared here have firing-statement duration.
  7    threshold CONSTANT SIMPLE_INTEGER := 200;
  8
  9    BEFORE STATEMENT IS
 10    BEGIN
 11      NULL;
 12    END BEFORE STATEMENT;
 13
 14    BEFORE EACH ROW IS
 15    BEGIN
 16      NULL;
 17    END BEFORE EACH ROW;
 18
 19    AFTER EACH ROW IS
 20    BEGIN
 21      NULL;
 22    END AFTER EACH ROW;
 23
 24    AFTER STATEMENT IS
 25    BEGIN
 26      NULL;
 27    END AFTER STATEMENT;
 28  END compound_trigger;
 29  /

Trigger created.

SQL>

複合トリガーを使用する2つの一般的な理由は次のとおりです。

複合トリガーの部分

複合トリガーには、宣言部と1つ以上のタイミング部があります。 同一のタイミングに対して複数の部分を持つことはできません。

オプションの宣言部(最初の部分)は、タイミング部で使用できる変数およびサブプログラムを宣言します。 トリガーが起動されると、タイミング部が実行される前に宣言部が実行されます。この部分で宣言された変数およびサブプログラムには、起動文の存続期間が含まれています。

ビューに対して定義された複合トリガーには、INSTEAD OF EACH ROWタイミング部があります。他のタイミング部はありません。

表に対して定義された複合トリガーには、表9-1に示すタイミング部が1つ以上あります。タイミング部は、表9-1に示す順序で配置する必要があります。タイミング部がない場合、そのタイミングでは何も実行されません。

タイミング部は、PL/SQLブロックで囲むことはできません。

表9-1に、表に対して定義できる複合トリガーのタイミング部の概要を示します。

表9-1 定義された複合トリガーのタイミング部

タイミング 部分

トリガーを起動する文が実行される前

BEFORE STATEMENT

トリガーを起動する文が実行された後

AFTER STATEMENT

トリガーを起動する文の影響を受ける各行の前

BEFORE EACH ROW

トリガーを起動する文の影響を受ける各行の後

AFTER EACH ROW


任意の部分に、InsertingUpdatingDeletingおよびApplyingファンクションを含めることができます。


参照:

複合トリガーの構文の詳細は、「CREATE TRIGGER文」を参照してください。

複合トリガーのトリガーを起動する文

複合トリガーのトリガーを起動する文はDML文である必要があります。

トリガーを起動する文が行に影響を与えず、複合トリガーにBEFORE STATEMENT部もAFTER STATEMENT部もない場合、トリガーは起動されません。

複合トリガーには、トリガーを起動する文が多数の行に影響を与える場合にパフォーマンス上のメリットがあります。このため、FORALL文でBULK COLLECT句を使用することが重要になります。たとえば、BULK COLLECT句を使用しない場合、INSERT文を含むFORALL文は単一行の挿入操作を何回も実行するのみのため、複合トリガーを使用するメリットはありません。 FORALL文でのBULK COLLECT句の使用の詳細は、「FORALLとBULK COLLECTの併用」を参照してください。

複合トリガーのトリガーを起動する文が、副問合せを含むINSERT文である場合、その複合トリガーではパフォーマンス上のメリットの一部が維持されます。たとえば、複合トリガーが次の文によってトリガーされるとします。

INSERT INTO Target
  SELECT c1, c2, c3
    FROM Source
      WHERE Source.c1 > 0

c1が0(ゼロ)よりも大きいSourceの各列に対して、複合トリガーのBEFORE EACH ROWおよびAFTER EACH ROWセクションが実行されます。ただし、BEFORE STATEMENTおよびAFTER STATEMENTセクションは(INSERT文が実行される前と後にそれぞれ)1回のみ実行されます。

複合トリガーの制限

  • 複合トリガーの本体は、複合トリガー・ブロックである必要があります。

  • 複合トリガーは、DMLトリガーである必要があります。

  • 複合トリガーは、表またはビューのいずれかに対して定義する必要があります。

  • 宣言部に、PRAGMA AUTONOMOUS_TRANSACTIONを含めることはできません。

  • 複合トリガー本体は、初期化ブロックを保持できません。したがって、例外部を保持できません。

    他のタイミング部が実行される前にBEFORE STATEMENT部が常に1回のみ実行されるため、これは問題となりません。

  • ある部分で発生した例外は、その部分で処理する必要があります。別の部分に制御を移すことはできません。

  • ある部分にGOTO文が含まれている場合、GOTO文のターゲットは同じ部分に存在している必要があります。

  • :OLD、:NEWおよび:PARENTは宣言部、BEFORE STATEMENT部またはAFTER STATEMENT部では使用できません。

  • BEFORE EACH ROW部のみが:NEWの値を変更できます。

  • 複合トリガーが起動された後にDML例外が発生したため、トリガーを起動する文がロールバックされた場合は、次の状態になります。

    • 複合トリガー部分で宣言されたローカル変数が再初期化され、それまでに計算された値が失われます。

    • 複合トリガーを起動したことによる副作用はロールバックされません。

  • 複合トリガーの起動順序は保証されません。これらの起動は、単純なトリガーの起動でインターリーブされる可能性があります。

  • FOLLOWSオプションを使用して複合トリガーを順序付けし、FOLLOWSのターゲットにソース・コードとして対応する部分が含まれていない場合、この順序付けは無視されます。

複合トリガーの例

使用例: 新しい表employee_salarieshr.employees.salaryに対するすべての変更を記録します。単一のUPDATE文がhr.employees表の多数の行を更新します。したがって、employee.salariesに行をバルク挿入する方が、行を個別に挿入するより効率的です。

解決方法: 例9-3に示すように、hr.employees表の更新に対して複合トリガーを定義します。idxまたはsalariesは、(トリガーを起動する文が中断され、再開された場合でも)トリガーが起動されるたびに初期化される状態変数であるため、これらを初期化するためにBEFORE STATEMENT部は必要ありません。

例9-3 複合トリガーによる、別の表へのある表に対する変更の記録

CREATE TABLE employee_salaries (
  employee_id NUMBER NOT NULL,
  change_date DATE   NOT NULL,
  salary NUMBER(8,2) NOT NULL,
  CONSTRAINT pk_employee_salaries PRIMARY KEY (employee_id, change_date),
  CONSTRAINT fk_employee_salaries FOREIGN KEY (employee_id)
    REFERENCES employees (employee_id)
      ON DELETE CASCADE)
/
CREATE OR REPLACE TRIGGER maintain_employee_salaries
  FOR UPDATE OF salary ON employees
    COMPOUND TRIGGER

-- Declarative Part:
-- Choose small threshhold value to show how example works:
  threshhold CONSTANT SIMPLE_INTEGER := 7;

  TYPE salaries_t IS TABLE OF employee_salaries%ROWTYPE INDEX BY SIMPLE_INTEGER;
  salaries  salaries_t;
  idx       SIMPLE_INTEGER := 0;

  PROCEDURE flush_array IS
    n CONSTANT SIMPLE_INTEGER := salaries.count();
  BEGIN
    FORALL j IN 1..n
      INSERT INTO employee_salaries VALUES salaries(j);
    salaries.delete();
    idx := 0;
    DBMS_OUTPUT.PUT_LINE('Flushed ' || n || ' rows');
  END flush_array;

  -- AFTER EACH ROW Section:

  AFTER EACH ROW IS
  BEGIN
    idx := idx + 1;
    salaries(idx).employee_id := :NEW.employee_id;
    salaries(idx).change_date := SYSDATE();
    salaries(idx).salary := :NEW.salary;
    IF idx >= threshhold THEN
      flush_array();
    END IF;
  END AFTER EACH ROW;

  -- AFTER STATEMENT Section:

  AFTER STATEMENT IS
  BEGIN
    flush_array();
  END AFTER STATEMENT;
END maintain_employee_salaries;
/
/* Increase salary of every employee in department 50 by 10%: */

UPDATE employees
  SET salary = salary * 1.1
  WHERE department_id = 50
/

/* Wait two seconds: */

BEGIN
  DBMS_LOCK.SLEEP(2);
END;
/

/* Increase salary of every employee in department 50 by 5%: */

UPDATE employees
  SET salary = salary * 1.05
  WHERE department_id = 50
/

複合トリガーを使用した変更表エラーの回避

複合トリガーを使用して、「変更表に対するトリガー制限」で説明されている変更表エラー(ORA-04091)を回避できます。

使用例: ビジネス・ルールに、従業員の昇給はその従業員の部門の平均給与の10%を超えてはならないと記載されています。このルールを、トリガーによって実行する必要があります。

解決方法: 例9-4に示すように、hr.employees表の更新に対して複合トリガーを定義します。(トリガーを起動する文が中断され、再開された場合でも)トリガーが起動されるたびに、状態変数は初期化されます。

例9-4 変更表エラーを回避する複合トリガー

CREATE OR REPLACE TRIGGER Check_Employee_Salary_Raise
  FOR UPDATE OF Salary ON Employees
COMPOUND TRIGGER
  Ten_Percent                 CONSTANT NUMBER := 0.1;
  TYPE Salaries_t             IS TABLE OF Employees.Salary%TYPE;
  Avg_Salaries                Salaries_t;
  TYPE Department_IDs_t       IS TABLE OF Employees.Department_ID%TYPE;
  Department_IDs              Department_IDs_t;

  TYPE Department_Salaries_t  IS TABLE OF Employees.Salary%TYPE
                                INDEX BY VARCHAR2(80);
  Department_Avg_Salaries     Department_Salaries_t;

  BEFORE STATEMENT IS
  BEGIN
    SELECT               AVG(e.Salary), NVL(e.Department_ID, -1)
      BULK COLLECT INTO  Avg_Salaries, Department_IDs
      FROM               Employees e
      GROUP BY           e.Department_ID;
    FOR j IN 1..Department_IDs.COUNT() LOOP
      Department_Avg_Salaries(Department_IDs(j)) := Avg_Salaries(j);
    END LOOP;
  END BEFORE STATEMENT;

  AFTER EACH ROW IS
  BEGIN
    IF :NEW.Salary - :Old.Salary >
      Ten_Percent*Department_Avg_Salaries(:NEW.Department_ID)
    THEN
      Raise_Application_Error(-20000, 'Raise too big');
    END IF;
  END AFTER EACH ROW;
END Check_Employee_Salary_Raise;

トリガー本体のコーディング


注意:

このトピックの内容は、単純なトリガーに主に適用されます。 複合トリガーの本体の書式は異なります(「複合トリガー」を参照)。

トリガー本体は、CALLサブプログラム(PL/SQLサブプログラムまたはPL/SQLラッパーにカプセル化されたJavaサブプログラム)またはPL/SQLブロックのいずれかです。このため、SQL文またはPL/SQL文を含めることができます。 これらの文は、トリガーを起動する文が入力され、トリガー制限(含まれている場合)がTRUEと評価された場合に実行されます。

行トリガーのトリガー本体がCALLサブプログラムではなくPL/SQLブロックである場合は、次の構造体を含めることができます。


参照:

この文の構文およびセマンティクスは、「CREATE TRIGGER文」を参照してください。

例9-5LOGONトリガーは、ユーザーがデータベースにログオンした後にプロシージャsec_mgr.check_userを実行します。 トリガー本体には例外処理部が含まれ、例外処理部にはRAISE_APPLICATION_ERRORを起動するWHEN OTHERS例外が含まれています。

例9-5 トリガーを使用したログオンの監視

CREATE OR REPLACE TRIGGER check_user
  AFTER LOGON ON DATABASE
  BEGIN
    sec_mgr.check_user;
  EXCEPTION
    WHEN OTHERS THEN
      RAISE_APPLICATION_ERROR
        (-20000, 'Unexpected error: '|| DBMS_Utility.Format_Error_Stack);
 END;
/

トリガーは、PL/SQLを使用して宣言されますが、他の言語のサブプログラムをコールできます。 例9-6のトリガーは、Javaサブプログラムを起動します。

例9-6 トリガーからのJavaサブプログラムの起動

CREATE OR REPLACE PROCEDURE Before_delete (Id IN NUMBER, Ename VARCHAR2)
IS language Java
name 'thjvTriggers.beforeDelete (oracle.sql.NUMBER, oracle.sql.CHAR)';

CREATE OR REPLACE TRIGGER Pre_del_trigger BEFORE DELETE ON Tab
FOR EACH ROW
CALL Before_delete (:OLD.Id, :OLD.Ename)
/

対応するJavaファイルはthjvTriggers.javaです。

import java.sql.*
import java.io.*
import oracle.sql.*
import oracle.oracore.*
public class thjvTriggers
{
public state void
beforeDelete (NUMBER old_id, CHAR old_name)
Throws SQLException, CoreException
   {
   Connection conn = JDBCConnection.defaultConnection();
   Statement stmt = conn.CreateStatement();
   String sql = "insert into logtab values
   ("+ old_id.intValue() +", '"+ old_ename.toString() + ", BEFORE DELETE');
   stmt.executeUpdate (sql);
   stmt.close();
   return;
   }
}

ここでのトピック:

行トリガーでの列値へのアクセス

行トリガーのトリガー本体内では、PL/SQLコードおよびSQL文によって、トリガーを起動する文によって影響を受ける現行の行に含まれている古い列値および新しい列値にアクセスできます。変更される表のすべての列に2つの相関名(古い列値および新しい列値用に1つずつ)が存在します。トリガーを起動する文の種類によっては、相関名が意味を持たない場合もあります。

  • INSERT文によって起動されるトリガーは、新しい列値に対してのみ意味のあるアクセスを行います。行はINSERTによって作成されるため、古い値はNULLです。

  • UPDATE文によって起動されるトリガーは、BEFOREおよびAFTERの両方の行トリガー用の古い列値および新しい列値の両方にアクセスします。

  • DELETE文によって起動されるトリガーは、:OLD列値に対してのみ意味のあるアクセスを行います。 行を削除するとその行は存在しなくなるため、:NEW値はNULLです。 ただし、:NEW値を変更しようとすると、ORA-4084が発生するため、:NEW値は変更できません。

新しい列値は列名の前にNEW修飾子を使用して参照し、元の列値は列名の前にOLD修飾子を使用して参照します。たとえば、トリガーを起動する文が(列SALCOMMなどを持つ)emp表に関連付けられている場合、トリガー本体に文を含めることができます。次に例を示します。

IF :NEW.Sal > 10000 ...
IF :NEW.Sal < :OLD.Sal ...

古い値および新しい値は、BEFOREおよびAFTER行トリガーの両方で使用できます。 NEW列値はBEFORE行トリガーには代入できますが、(AFTER行トリガーが起動される前にトリガーを起動する文が有効となるため)AFTER行トリガーには代入できません。 BEFORE行トリガーによってNEW.columnの値が変更されると、同じ文によって起動されるAFTER行トリガーは、BEFORE行トリガーによって代入された変更を参照します。

WHEN句のブール式には相関名を使用することもできます。 OLDおよびNEW修飾子をトリガー本体で使用する場合は、修飾子の前にコロン(:)を付ける必要があります。ただし、修飾子をWHEN句またはREFERENCINGオプションで使用する場合、コロンは使用できません。

例: トリガーを使用したLOB列の変更

CLOB列を持つ通常のSQLファンクションとPL/SQLファンクションおよびBLOB列を持つDBMS_LOBパッケージへのコールを使用して、LOB列を他の列と同様に処理できます。

drop table tab1;

create table tab1 (c1 clob);
insert into tab1 values ('<h1>HTML Document Fragment</h1><p>Some text.');

create or replace trigger trg1
  before update on tab1
  for each row
begin
  dbms_output.put_line('Old value of CLOB column: '||:OLD.c1);
  dbms_output.put_line('Proposed new value of CLOB column: '||:NEW.c1);

-- Previously, you couldn't change the new value for a LOB.
-- Now, you can replace it, or construct a new value using SUBSTR, INSTR...
-- operations for a CLOB, or DBMS_LOB calls for a BLOB.
  :NEW.c1 := :NEW.c1 || to_clob('<hr><p>Standard footer paragraph.');

  dbms_output.put_line('Final value of CLOB column: '||:NEW.c1);
end;
/

set serveroutput on;
update tab1 set c1 = '<h1>Different Document Fragment</h1><p>Different text.';

select * from tab1;

ネストした表のビューの列に対するINSTEAD OFトリガー

ネストした表のビューの列に対するINSTEAD OFトリガーの場合は、NEWおよびOLD修飾子が、ネストした表の新しい要素および古い要素に対応します。このネストした表の要素に対応する親行には、parent修飾子を使用してアクセスできます。親の相関名は、ネストした表のトリガー内でのみ意味があり有効です。

トリガーとの名前の競合の回避(REFERENCINGオプション)

行トリガーのトリガー本体にREFERENCINGオプションを指定すると、OLDまたはNEWと名前指定される相関名と表の間での名前の競合を回避できます。このようなことはほとんど発生しないため、このオプションはほとんど使用されません。

たとえば、表newが次のように作成されたとします。

CREATE TABLE new (
   field1     NUMBER,
   field2     VARCHAR2(20));

次のCREATE TRIGGERの例は、相関名を使用し、相関名と表名の間での名前の競合を回避できる、表newに対して定義されたトリガーの例です。

CREATE OR REPLACE TRIGGER Print_salary_changes
BEFORE UPDATE ON new
REFERENCING new AS Newest
FOR EACH ROW
BEGIN
   :Newest.Field2 := TO_CHAR (:newest.field1);
END;

REFERENCINGオプションを使用してNEW修飾子をnewestに変更し、その修飾子をトリガー本体で使用していることに注意してください。

トリガーを起動したDML操作の検出

種類が異なる複数のDML操作(ON INSERT OR DELETE OR UPDATE OF empなど)でトリガーを起動できる場合は、トリガー本体で条件述語INSERTINGDELETINGおよびUPDATINGを使用して、トリガーを起動する文の種類を確認できます。

トリガー本体のコード内では、トリガーを起動したDML操作の種類に応じて、次のコード・ブロックを実行できます。

IF INSERTING THEN ... END IF;
IF UPDATING THEN ... END IF;

最初の条件は、トリガーを起動した文がINSERT文の場合にのみTRUEと評価されます。2番目の条件は、トリガーを起動した文がUPDATE文の場合にのみTRUEと評価されます。

UPDATEトリガーでは、UPDATING条件述語で列名を指定して、指定した列が更新されているかどうかを判断できます。たとえば、トリガーが次のように定義されているとします。

CREATE OR REPLACE TRIGGER ...
... UPDATE OF Sal, Comm ON emp ...
BEGIN

... IF UPDATING ('SAL') THEN ... END IF;

END;

THEN句のコードは、トリガーを起動するUPDATE文がSAL列を更新する場合にのみ実行されます。このように、対象の列が変更されていない場合は、トリガーはオーバーヘッドを最小化できます。

トリガー本体内のエラー条件および例外

トリガー本体の実行中に、事前定義またはユーザー定義のエラー条件(例外)が呼び出されると、(エラーが例外ハンドラによってトラップされないかぎり)トリガーを起動する文のみでなくトリガー本体のすべての影響がロールバックされます。したがって、トリガー本体は、例外を呼び出すことによって、トリガーを起動する文を実行する必要がなくなります。ユーザー定義の例外は、複雑なセキュリティ認可または制約を実行するトリガーでよく使用されます。

LOGONトリガーによって例外が呼び出されると、次の場合を除いてログオンは失敗します。

  • データベースの起動および停止操作は、これらのイベントのシステム・トリガーによって例外が呼び出された場合でも失敗しません。トリガー・アクションのみがロールバックされます。エラーは、トレース・ファイルおよびアラート・ログに記録されます。

  • システム・トリガーがDATABASE LOGONトリガーであり、ユーザーがADMINISTER DATABASE TRIGGER権限を持っているとき、トリガーによって例外が呼び出された場合でも、ユーザーは正常にログオンできます。 SCHEMA LOGONトリガーでは、ログオンするユーザーがトリガー所有者であるか、またはALTER ANY TRIGGER権限を持っている場合、ログオンは許可されます。トリガー・アクションのみがロールバックされます。エラーは、トレース・ファイルおよびアラート・ファイルに記録されます。

オブジェクト表に対するトリガー

10g リリース1(10.1)以上では、OBJECT_VALUEがオブジェクト全体を表すため、オブジェクト表に対するトリガーではOBJECT_VALUE擬似列を使用できます。これは1つの使用例です。 また、OBJECT_VALUEIN仮パラメータのデータ型として指定してPL/SQLファンクションを起動することもできます。

次に、トリガー内でのOBJECT_VALUEの使用例を示します。また、次の例では、オブジェクト表tbl内の値に対する更新を追跡するために、履歴表tbl_historyも作成されます。tblの場合、1から5までの値がnに挿入されます。mは常に0(ゼロ)です。トリガーは、DML文によって影響を受ける各行に対して1回実行される行レベルのトリガーです。tblが更新されると、トリガーによって、tbl内のオブジェクトtの古い値および新しい値がtbl_historyに書き込まれます。これらの古い値および新しい値は、:OLD.OBJECT_VALUEおよび:NEW.OBJECT_VALUEです。表tblが更新されます(nの各値が1増加します)。トリガーが動作していることを確認するために、履歴表からのSELECTの結果が例の最後に示されます。

CREATE OR REPLACE TYPE t AS OBJECT (n NUMBER, m NUMBER)
/
CREATE TABLE tbl OF t
/
BEGIN
  FOR j IN 1..5 LOOP
    INSERT INTO tbl VALUES (t(j, 0));
  END LOOP;
END;
/
CREATE TABLE tbl_history ( d DATE, old_obj t, new_obj t)
/
CREATE OR REPLACE TRIGGER Tbl_Trg
AFTER UPDATE ON tbl
FOR EACH ROW
BEGIN
  INSERT INTO tbl_history (d, old_obj, new_obj)
    VALUES (SYSDATE, :OLD.OBJECT_VALUE, :NEW.OBJECT_VALUE);
END Tbl_Trg;
/
--------------------------------------------------------------------------------

UPDATE tbl SET tbl.n = tbl.n+1
/
BEGIN
  FOR j IN (SELECT d, old_obj, new_obj FROM tbl_history) LOOP
    Dbms_Output.Put_Line (
      j.d||
      ' -- old: '||j.old_obj.n||' '||j.old_obj.m||
      ' -- new: '||j.new_obj.n||' '||j.new_obj.m);
  END LOOP;
END;
/

このSELECTの結果は、列nのすべての値が1増加していることを示しています。mの値は0(ゼロ)のままです。このSELECTの出力は次のようになります。

23-MAY-05 -- old: 1 0 -- new: 2 0
23-MAY-05 -- old: 2 0 -- new: 3 0
23-MAY-05 -- old: 3 0 -- new: 4 0
23-MAY-05 -- old: 4 0 -- new: 5 0
23-MAY-05 -- old: 5 0 -- new: 6 0

トリガーおよびリモート例外処理

リモート・サイトにアクセスするトリガーは、ネットワーク・リンクが使用できない場合はリモート例外処理を実行できません。次に例を示します。

CREATE OR REPLACE TRIGGER Example
  AFTER INSERT ON emp
    FOR EACH ROW
BEGIN
  When dblink is inaccessible, compilation fails here:
  INSERT INTO emp@Remote VALUES ('x');
EXCEPTION
  WHEN OTHERS THEN
    INSERT INTO Emp_log VALUES ('x');
END;

トリガーは、作成時にコンパイルされます。 したがって、トリガーをコンパイルする必要がある場合にリモート・サイトを使用できないと、データベースでリモート・データベースにアクセスする文の妥当性チェックを実行できないため、コンパイルは正常に実行されません。前述の例外文の例は、トリガーがコンパイルを完了しないため実行できません。

ストアド・サブプログラムはコンパイル済の形式で格納されるため、前述の例の回避方法は次のようになります。

CREATE OR REPLACE TRIGGER Example
  AFTER INSERT ON emp
    FOR EACH ROW
BEGIN
  Insert_row_proc;
END;

CREATE OR REPLACE PROCEDURE Insert_row_proc AS
BEGIN
  INSERT INTO emp@Remote VALUES ('x');
EXCEPTION
  WHEN OTHERS THEN
  INSERT INTO Emp_log VALUES ('x');
END;

この例のトリガーは、正常にコンパイルされ、リモート・データベースにアクセスするための妥当性チェック済の文がすでに含まれているストアド・サブプログラムを起動します。したがって、リンクを使用できないためリモートINSERT文が失敗すると、例外が捕捉されます。

トリガー作成の制限

トリガーのコーディングには、標準PL/SQLブロックでは必要とされないいくつかの制限があります。

ここでのトピック:

トリガーの最大サイズ

トリガーのサイズは、32K以下にしてください。

トリガー本体で使用可能なSQL文

トリガー本体には、SELECT INTO文、カーソル定義内のSELECT文および他のすべてのDML文を含めることができます。

システム・トリガー本体には、DDL文CREATETABLEALTERTABLEDROP TABLEおよびALTER COMPILEを含めることができます。非システム・トリガー本体には、DDL文またはトランザクション制御文を含めることはできません。


注意:

トリガーによって起動されるサブプログラムは、トリガー本体のコンテキスト内で実行されるため、このようなサブプログラムでは前述のトランザクション制御文は実行できません。

トリガー内の文で、リモート・スキーマ・オブジェクトを参照できます。ただし、ローカル・トリガー内からリモート・サブプログラムを起動する場合は、特に注意が必要です。トリガーの実行中にタイムスタンプまたはシグネチャの不一致が検出されると、リモート・サブプログラムは実行されず、トリガーは無効になります。

LONGおよびLONG RAWデータ型に対するトリガー制限

トリガー内のLONGおよびLONG RAWデータ型には、次の制限があります。

  • トリガー内のSQL文で、LONGまたはLONG RAWデータ型の列にデータを挿入できます。

  • LONGまたはLONG RAW列のデータを制約データ型(CHARVARCHAR2など)に変換できる場合、トリガー内のSQL文でLONGまたはLONG RAW列を参照できます。 これらのデータ型の最大長は32000バイトです。

  • LONGまたはLONG RAWデータ型を使用して変数を宣言することはできません。

  • :LONGまたはLONG RAW列では、:NEWおよび:PARENTは使用できません。

変更表に対するトリガー制限

変更表とは、UPDATE文、DELETE文またはINSERT文で現在変更されている表、あるいはDELETE CASCADE制約の影響によって更新される可能性がある表のことです。

トリガーを起動する文を発行したセッションは、変更表の問合せまたは変更は実行できません。この制限のため、トリガーは一貫性のないデータは参照しません。

この制限は、FOR EACH ROW句を使用するすべてのトリガーに適用されます。INSTEAD OFトリガー内で変更中のビューは、変更ビューとはみなされません。

トリガーで変更表が検出されると、ランタイム・エラーが発生し、トリガー本体の影響およびトリガーを起動する文がロールバックされ、ユーザーまたはアプリケーションに制御が戻されます。(複合トリガーを使用して変更表エラーを回避できます。 詳細は、「複合トリガーを使用した変更表エラーの回避」を参照してください。)

次のトリガーについて考えてみます。

CREATE OR REPLACE TRIGGER Emp_count
  AFTER DELETE ON emp
    FOR EACH ROW
DECLARE
  n INTEGER;
BEGIN
  SELECT COUNT(*) INTO n FROM emp;
  DBMS_OUTPUT.PUT_LINE('There are now ' || n || ' employees.');
END;

次のSQL文を入力したとします。

DELETE FROM emp WHERE empno = 7499;

行の削除時に表が変更中であるため、次のエラーが戻されます。

ORA-04091: table HR.emp is mutating, trigger/function might not see it

このトリガーからFOR EACH ROW行を削除すると、このトリガーはこの制限の影響を受けない文トリガーとなり、トリガーとして正常に動作するようになります。

変更表を更新する必要がある場合、一時表、PL/SQL表またはパッケージ変数を使用してこれらの制限を回避することもできます。たとえば、元の表を更新する1つのAFTER行トリガーで変更表エラーが発生する場合、かわりに、一時表を更新するAFTER行トリガーおよび一時表からの値を使用して元の表を更新するAFTER文トリガーの2つのトリガーを使用できます。

宣言制約は、行トリガーに関して随時チェックされます。


参照:

トリガーと制約の相互作用の詳細は、『Oracle Database概要』を参照してください。

分散データベースの異なるノード上の表の間では、宣言参照制約はサポートされていないため、変更表の制限は、リモート・ノードにアクセスするトリガーには適用されません。これらの制限は、ループバック・データベース・リンクで接続されている同一データベース内の表の間でも適用されません。ループバック・データベース・リンクによって、リンクを含むデータベースに戻るOracle Netパスが定義され、ローカル表がリモートで表示されます。

変更表に対する制限の緩和

「変更表に対するトリガー制限」で説明されているように、変更エラーが存在すると、親文が変更する表に対してトリガーで読取りまたは変更を行うことはできません。ただし、Oracleデータベースリリース8.1以上では、親表に対して削除を行うと、BEFOREおよびAFTERトリガーが1回起動されます。したがって、(行トリガー以外の)トリガーを作成して親表および子表の読取りおよび変更を実行できます。

これによって、ほとんどの外部キー制約アクションはそれらの明白なAFTER行トリガーを介して実装されるため、制約は自己参照的でなくなります。更新カスケード、更新セットNULL、更新セット・デフォルト、削除セット・デフォルト、欠落した親の挿入および子のカウントの保持をすべて簡単に実装できます。次に、更新カスケードの実装の例を示します。

CREATE TABLE p (p1 NUMBER CONSTRAINT pk_p_p1 PRIMARY KEY);
CREATE TABLE f (f1 NUMBER CONSTRAINT fk_f_f1 REFERENCES p);
CREATE TRIGGER pt AFTER UPDATE ON p FOR EACH ROW BEGIN
  UPDATE f SET f1 = :NEW.p1 WHERE f1 = :OLD.p1;
END;
/

この実装では、複数行を更新する場合に注意が必要です。たとえば、表pに値(1)、(2)、(3)を持つ3つの行があり、表fにも値(1)、(2)、(3)を持つ3つの行がある場合、次の文によってpは正常に更新されますが、トリガーがfを更新するときに問題が発生します。

UPDATE p SET p1 = p1+1;

まず、この文はpの値(1)から(2)への更新を行い、トリガーはfに値(2)の行を2つ残したままfの値(1)から(2)への更新を行います。次に、この文はpの値(2)から(3)への更新を行い、トリガーはfの2つの行の値を両方とも(2)から(3)へ更新します。最後に、この文はpの値(3)から(4)への更新を行い、トリガーはfの3つすべての行を(3)から(4)へ更新します。pfのデータの関係は失われます。

この問題を回避するには、主キーを変更するpの複数行更新を禁止して既存の主キー値を再利用するか、または外部キー値の更新を追跡して行が2回更新されないようにトリガーを変更します。

これが、外部キーの更新に関するこの方法の唯一の問題です。トリガーは、別のトランザクションによってコミットされていない変更済の行を見逃すことはありません。外部キー制約によって、AFTER行トリガーが起動されるまでいずれの一致する外部キー行もロックされないことが保証されているためです。

システム・トリガーの制限

イベントに応じて、使用できるイベント属性ファンクションは異なります。たとえば、特定のDDL操作をDDLイベントに対して使用できない場合があります。 イベント属性ファンクションの影響は、エラー条件を生成するのではなく、未定義の場合があるため、イベント属性ファンクションを使用する前に、「イベント属性ファンクション」を参照してください。

コミットされたトリガーのみが起動されます。たとえば、すべてのCREATEイベントの後に起動されるトリガーを作成した場合、作成後にこのトリガー自体は起動されません。CREATEイベントに対するトリガーが起動された時点では、このトリガーに関する正しい情報はまだコミットされていなかったためです。

たとえば、次のSQL文を実行するとします。

CREATE OR REPLACE TRIGGER my_trigger AFTER CREATE ON DATABASE
BEGIN null;
END;

トリガーmy_triggerは、my_triggerの作成後には起動されません。 データベースでは、コミットされていないトリガーは起動されません。

外部ファンクションのコールアウト

外部ファンクションのコールアウトに関するすべての制限も適用されます。

トリガーを使用するユーザー

次の文では、トリガーに、トリガーの所有者は戻されますが、表を更新しているユーザーの名前は戻されません。

SELECT Username FROM USER_USERS;

トリガーのコンパイル

トリガーと無名PL/SQLブロックの重要な違いはコンパイル方法です。無名PL/SQLブロックは、メモリーにロードされるたびにコンパイルされます。このコンパイルには、次の3段階があります。

  1. 構文チェック: PL/SQL構文がチェックされ、解析ツリーが生成されます

  2. セマンティック・チェック: 型チェックおよび解析ツリーに対する追加処理が行われます。

  3. コード生成

トリガーは、CREATE TRIGGER文の実行時に完全にコンパイルされます。トリガー・コードはデータ・ディクショナリに格納されます。そのため、トリガーの実行に共有カーソルをオープンする必要はなくなります。トリガーは直接実行されます。

トリガーのコンパイル中にエラーが発生しても、トリガーは作成されます。したがって、DML文がこのトリガーを起動すると、(トリガーが無効な状態で作成されていないかぎり)そのDML文は失敗します。トリガーのコンパイル・エラーを表示するには、SQL*PlusまたはOracle Enterprise ManagerでSHOW ERRORS文を使用するか、またはUSER_ERRORSビューに対してエラーのSELECTを実行します。

ここでのトピック:

トリガーの依存性

コンパイル済のトリガーには依存性があります。コンパイル済のトリガーは、トリガー本体から起動されるストアド・サブプログラムなどの依存先のオブジェクトが変更されると無効になります。依存性のため無効になったトリガーは、次回の起動時に再コンパイルされます。

ALL_DEPENDENCIESビューを調べると、トリガーの依存性を確認できます。たとえば、次の文は、HRスキーマ内のトリガーの依存性を示します。

SELECT NAME, REFERENCED_OWNER, REFERENCED_NAME, REFERENCED_TYPE
    FROM ALL_DEPENDENCIES
    WHERE OWNER = 'HR' and TYPE = 'TRIGGER';

トリガーは、他のファンクションまたはパッケージに依存する場合があります。トリガー内に指定されているファンクションまたはパッケージが削除されると、トリガーは無効とマークされます。イベントの発生時に、トリガーの検証が試行されます。トリガーを正常に検証できなかった場合、そのトリガーはVALID WITH ERRORSとマークされ、イベントは失敗します。スキーマ・オブジェクト間の依存性の詳細は、『Oracle Database概要』を参照してください。


注意:

  • STARTUPイベントに関しては例外が1つあります。STARTUPイベントは、トリガーが失敗しても正常に実行されます。SYSTEMとしてログインした場合は、SHUTDOWNイベントおよびLOGONイベントに関しても例外があります。

  • メッセージのエンキューにはDBMS_AQパッケージが使用されるため、トリガーとキューの間の依存性は維持できません。


トリガーの再コンパイル

トリガーを手動で再コンパイルするには、ALTER TRIGGER文を使用します。たとえば、次の文はPRINT_SALARY_CHANGESトリガーを再コンパイルします。

ALTER TRIGGER Print_salary_changes COMPILE;

トリガーを再コンパイルするには、トリガーを所有しているか、またはALTER ANY TRIGGERシステム権限が必要です。

トリガーの変更

ストアド・サブプログラムと同様に、トリガーは明示的に変更できません。新しい定義と置き換える必要があります。(ALTER TRIGGER文は、トリガーの再コンパイル、有効化または無効化を行うためにのみ使用します。)

トリガーを置き換える場合は、CREATE TRIGGER文にOR REPLACEオプションを指定する必要があります。OR REPLACEオプションを使用することによって、トリガーの元のバージョンに対して行われる権限付与に影響を与えずに、既存のトリガーの古いバージョンを新しいバージョンに置き換えることができます。

また、トリガーはDROP TRIGGER文を使用して削除でき、削除してからCREATE TRIGGER文を再実行できます。

トリガーを削除するには、トリガーが自スキーマ内にあるか、またはDROP ANY TRIGGERシステム権限を所有している必要があります。

トリガーのデバッグ

ストアド・サブプログラムで使用可能な機能と同じ機能を使用して、トリガーをデバッグできます。詳細は、『Oracle Databaseアドバンスト・アプリケーション開発者ガイド』を参照してください。

トリガーの有効化

無効になっているトリガーを有効にするには、ENABLE句を指定してALTER TRIGGER文を使用します。たとえば、無効になっているReorderというトリガーを有効にするには、次の文を入力します。

ALTER TRIGGER Reorder ENABLE;

特定の表に定義されているすべてのトリガーを有効にするには、ENABLE句とALL TRIGGERSオプションを指定してALTER TABLE文を使用します。たとえば、Inventory表に定義されているすべてのトリガーを有効にするには、次の文を入力します。

ALTER TABLE Inventory ENABLE ALL TRIGGERS;

トリガーの無効化

次のような場合、トリガーを一時的に無効にできます。

トリガーを無効にするには、DISABLEオプションを指定してALTER TRIGGER文を使用します。たとえば、Reorderというトリガーを無効にするには、次の文を入力します。

ALTER TRIGGER Reorder DISABLE;

特定の表に定義されているすべてのトリガーを無効にするには、DISABLE句とALL TRIGGERSオプションを指定してALTER TABLE文を使用します。たとえば、Inventory表に定義されているすべてのトリガーを無効にするには、次の文を入力します。

ALTER TABLE Inventory DISABLE ALL TRIGGERS;

トリガーに関する情報の表示

*_TRIGGERS静的データ・ディクショナリ・ビューには、トリガーに関する情報が表示されます。

BASE_OBJECT_TYPEは、トリガーがDATABASESCHEMA、表またはビューのいずれに基づいているかを示します。ベース・オブジェクトが表またはビューでない場合、列TABLE_NAMEはNULLです。

ACTION_TYPEは、トリガーがコール型のトリガーかPL/SQLトリガーかを示します。

TRIGGER_TYPEは、トリガーのタイプを示します。たとえば、COMPOUNDBEFORE EVENTAFTER EVENTなどです(最後の2つはデータベース・イベントにのみ適用されます)。

BEFORE_STATEMENTBEFORE_ROWAFTER_ROWAFTER_STATEMENTおよびINSTEAD_OF_ROWの各列の値はYESまたはNOです。

TRIGGERING_EVENTには、すべてのシステム・イベントおよびDMLイベントが含まれます。


参照:

静的データ・ディクショナリ・ビュー*_TRIGGERSの詳細は、『Oracle Databaseリファレンス』を参照してください。

たとえば、Reorderトリガーの作成に次の文が使用されたとします。

CREATE OR REPLACE TRIGGER Reorder
AFTER UPDATE OF Parts_on_hand ON Inventory
FOR EACH ROW
WHEN(NEW.Parts_on_hand < NEW.Reorder_point)
DECLARE
   x NUMBER;
BEGIN
   SELECT COUNT(*) INTO x
      FROM Pending_orders
      WHERE Part_no = :NEW.Part_no;
   IF x = 0  THEN
      INSERT INTO Pending_orders
         VALUES (:NEW.Part_no, :NEW.Reorder_quantity,
                 sysdate);
   END IF;
END;

次の2つの問合せは、REORDERトリガーに関する情報を戻します。

SELECT Trigger_type, Triggering_event, Table_name
   FROM USER_TRIGGERS
   WHERE Trigger_name = 'REORDER';

TYPE             TRIGGERING_STATEMENT       TABLE_NAME
---------------- -------------------------- ------------
AFTER EACH ROW   UPDATE                     INVENTORY

SELECT Trigger_body
   FROM USER_TRIGGERS
   WHERE Trigger_name = 'REORDER';

TRIGGER_BODY
--------------------------------------------
DECLARE
   x NUMBER;
BEGIN
   SELECT COUNT(*) INTO x
      FROM Pending_orders
      WHERE Part_no = :NEW.Part_no;
   IF x = 0
      THEN INSERT INTO Pending_orders
         VALUES (:NEW.Part_no, :NEW.Reorder_quantity,
            sysdate);
   END IF;
END;

トリガー・アプリケーションの例

様々な方法でトリガーを使用して、データベースの情報管理をカスタマイズできます。たとえば、トリガーは、通常、次の用途に使用します。

この項では、これらのトリガー・アプリケーションの例を示します。これらの例はそのままでは使用できませんが、トリガーを設計する場合の参考にしてください。

トリガーを使用した監査

トリガーは、データベースの組込み監査機能を補うためによく使用されます。トリガーは、AUDIT文によって記録される情報と同様の情報を記録するために作成できますが、より詳細な監査情報が必要な場合にのみ使用します。たとえば、トリガーを使用すると、各行に対して値ベースの監査を実行できます。

AUDIT文はセキュリティ監査機能とみなされる場合があります。一方、トリガーは財務監査機能を提供できます。

データベース・アクティビティを監査するトリガーを作成するかどうかを判断する場合は、トリガーで定義される監査と比較してデータベースの監査機能で提供される内容について検討します(表9-2を参照)。

表9-2 組込み監査とトリガー・ベースの監査の比較

監査機能 説明

DMLおよびDDLの監査

標準監査オプションでは、すべてのタイプのスキーマ・オブジェクトと構造体に関するDML文およびDDL文の監査を行うことができます。これと比較して、トリガーでは、表に対して入力されたDML文の監査およびSCHEMAまたはDATABASEレベルでのDDLの監査を行うことができます。

集中型監査証跡

すべてのデータベース監査情報は、データベースの監査機能を使用して集中的かつ自動的に記録されます。

宣言方式

トリガーで定義された監査機能と比較すると、データベースの標準機能で使用できる監査機能は宣言およびメンテナンスが簡単で、エラーが発生する可能性が低くなります。

監査オプションの監査

既存の監査オプションに対する変更を監査することによっても、悪意のあるデータベース・アクティビティを回避できます。

セッションおよび実行時の監査

データベース監査機能を使用して、監査文を入力するたびに1回(BY ACCESS)、または監査文を入力するセッションごとに1回(BY SESSION)、レコードを生成できます。トリガーでは、セッション単位の監査を行うことはできません。監査レコードは、トリガーで監査される表が参照されるたびに生成されます。

失敗したデータ・アクセスの監査

データ・アクセスが失敗した場合に監査が実行されるようにデータベース監査を設定できます。ただし、自律型トランザクションが使用されないかぎり、トリガーを起動する文がロールバックされると、トリガーによって生成された監査情報もロールバックされます。自律型トランザクションの詳細は、『Oracle Database概要』を参照してください。

セッションの監査

標準データベース監査機能を使用して、セッション・アクティビティ(物理I/O、論理I/O、デッドロックなど)とともに接続および切断を記録できます。


トリガーを使用して高度な監査を行う場合は、通常、AFTERトリガーを使用します。トリガーを起動する文は、適用可能な制約に影響を受けます。レコードが検出されなかった場合、AFTERトリガーは起動されず、監査処理は不必要には実行されません。

AFTER行トリガーとAFTER文トリガーのいずれを選択するかは、監査情報によって異なります。たとえば、行トリガーを使用すると、各表の行に対して値ベースの監査を実行できます。トリガーでは、監査済のSQL文を発行するための理由コードの入力をユーザーに要求することもできます。これは、行レベルおよび文レベルの両方の監査状況で有効です。

次に、emp表に対する変更を行ごとに監査するトリガーの例を示します。この例では、更新前に理由コードをグローバル・パッケージ変数に格納する必要があります。トリガーを使用して値ベースの監査を実行する方法、およびパブリック・パッケージ変数を使用する方法が示されています。


注意:

例を実行するには、次のデータ構造を設定する必要がある場合があります。
CREATE OR REPLACE PACKAGE Auditpackage AS
   Reason VARCHAR2(10);
PROCEDURE Set_reason(Reason VARCHAR2);
END;
CREATE TABLE Emp99 (
   Empno               NOT NULL   NUMBER(4),
   Ename               VARCHAR2(10),
   Job                 VARCHAR2(9),
   Mgr                 NUMBER(4),
   Hiredate            DATE,
   Sal                 NUMBER(7,2),
   Comm                NUMBER(7,2),
   Deptno              NUMBER(2),
   Bonus               NUMBER,
   Ssn                 NUMBER,
   Job_classification  NUMBER);

CREATE TABLE Audit_employee (
   Oldssn              NUMBER,
   Oldname             VARCHAR2(10),
   Oldjob              VARCHAR2(2),
   Oldsal              NUMBER,
   Newssn              NUMBER,
   Newname             VARCHAR2(10),
   Newjob              VARCHAR2(2),
   Newsal              NUMBER,
   Reason              VARCHAR2(10),
   User1               VARCHAR2(10),
   Systemdate          DATE);

CREATE OR REPLACE TRIGGER Audit_employee
AFTER INSERT OR DELETE OR UPDATE ON Emp99
FOR EACH ROW
BEGIN
/* AUDITPACKAGE is a package with a public package
   variable REASON. REASON can be set by the
   application by a statement such as EXECUTE
   AUDITPACKAGE.SET_REASON(reason_string).
   A package variable has state for the duration of a
   session and that each session has a separate copy of
   all package variables. */

IF Auditpackage.Reason IS NULL THEN
   Raise_application_error(-20201, 'Must specify reason'
      || ' with AUDITPACKAGE.SET_REASON(Reason_string)');
END IF;

/* If preceding condition evaluates to TRUE,
   user-specified error number & message is raised,
   trigger stops execution, & effects of triggering statement are rolled back.
   Otherwise, new row is inserted
   into predefined auditing table named AUDIT_EMPLOYEE
   containing existing & new values of the emp table
   & reason code defined by REASON variable of AUDITPACKAGE.
   "Old" values are NULL if triggering statement is INSERT
   & "new" values are NULL if triggering statement is DELETE. */

INSERT INTO Audit_employee VALUES (
  :OLD.Ssn, :OLD.Ename, :OLD.Job_classification, :OLD.Sal,
  :NEW.Ssn, :NEW.Ename, :NEW.Job_classification, :NEW.Sal,
  auditpackage.Reason, User, Sysdate
);
END;

オプションで、更新のたびに理由コードを強制的に設定する場合は、理由コードをNULLに設定しなおすこともできます。次の単純なAFTER文トリガーによって、トリガーを起動する文が実行された後に理由コードがNULLに設定しなおされます。

CREATE OR REPLACE TRIGGER Audit_employee_reset
  AFTER INSERT OR DELETE OR UPDATE ON emp
BEGIN
   auditpackage.set_reason(NULL);
END;

前述の2つのトリガーは、同じ種類のSQL文によって起動されます。ただし、AFTER行トリガーが、トリガーを起動する文によって影響を受ける表の行ごとに1回起動されるのに対して、AFTER文トリガーは、トリガーを起動する文の実行が完了した後に1回のみ起動されます。

次に示すトリガーもトリガーを使用して監査を行います。このトリガーは、emp表に行われた変更を追跡し、その情報をaudit_tableおよびaudit_table_valuesに格納します。


注意:

この例を実行するには、次のデータ構造を設定する必要がある場合があります。
CREATE TABLE audit_table (
   Seq      NUMBER,
   User_at  VARCHAR2(10),
   Time_now DATE,
   Term     VARCHAR2(10),
   Job      VARCHAR2(10),
   Proc     VARCHAR2(10),
   enum     NUMBER);
CREATE SEQUENCE audit_seq;
CREATE TABLE audit_table_values (
   Seq      NUMBER,
   Dept     NUMBER,
   Dept1    NUMBER,
   Dept2    NUMBER);

CREATE OR REPLACE TRIGGER Audit_emp
  AFTER INSERT OR UPDATE OR DELETE ON emp
    FOR EACH ROW
  DECLARE
    Time_now DATE;
    Terminal CHAR(10);
  BEGIN
    -- Get current time, & terminal of user:
    Time_now := SYSDATE;
    Terminal := USERENV('TERMINAL');

    -- Record new employee primary key:
    IF INSERTING THEN
      INSERT INTO audit_table VALUES (
        Audit_seq.NEXTVAL, User, Time_now,
        Terminal, 'emp', 'INSERT', :NEW.Empno
      );

      -- Record primary key of deleted row:
      ELSIF DELETING THEN
        INSERT INTO audit_table  VALUES (
          Audit_seq.NEXTVAL, User, Time_now,
          Terminal, 'emp', 'DELETE', :OLD.Empno
        );

      -- For updates, record primary key of row being updated:
      ELSE
        INSERT INTO audit_table VALUES (
          audit_seq.NEXTVAL, User, Time_now,
          Terminal, 'emp', 'UPDATE', :OLD.Empno
        );

         -- For SAL & DEPTNO, record old & new values:
         IF UPDATING ('SAL') THEN
           INSERT INTO audit_table_values VALUES (
             Audit_seq.CURRVAL, 'SAL',
             :OLD.Sal, :NEW.Sal
           );

         ELSIF UPDATING ('DEPTNO') THEN
           INSERT INTO audit_table_values VALUES (
             Audit_seq.CURRVAL, 'DEPTNO',
            :OLD.Deptno, :NEW.DEPTNO
           );
         END IF;
      END IF;
END;

制約およびトリガー

トリガーおよび宣言制約は、両方ともデータ入力の制約に使用できます。ただし、トリガーと制約には大きな違いがあります。

宣言制約は、データベースに関する文で、常にTRUEです。表内の既存のデータおよび表を操作するすべての文に対して制約が適用されます。


参照:

『Oracle Databaseアドバンスト・アプリケーション開発者ガイド』

トリガーは、トランザクションで実行可能な処理を制約します。トリガーは、トリガーが定義される前にロードされたデータには適用されません。このため、表内のすべてのデータが、関連付けられているトリガーによって確立されたルールに準拠しているかどうかは不明です。

トリガーは、宣言制約機能でサポートされているルールと同じルールの多くを実行するために作成できますが、標準の制約では定義できない複雑なビジネス・ルールを実行するためにのみ使用してください。 データベースの宣言制約機能には、トリガーで定義する制約と比較すると、次のメリットがあります。

  • 集中型整合性チェック

    すべてのデータ・アクセス・ポイントは、各スキーマ・オブジェクトに対応する制約によって定義された一連のグローバルなルールに準拠している必要があります。

  • 宣言方式

    標準の制約機能を使用して定義された制約は、トリガーで定義された制約と同等の制約と比較すると、より作成しやすくエラーが発生する可能性が低くなります。

データ整合性のほとんどの側面は、宣言制約で定義および実行できますが、トリガーは、宣言制約では定義できない複雑なビジネス制約の実行に使用できます。たとえば、トリガーを使用して次のものを実行できます。

  • UPDATE SET NULLUPDATE SET DEFAULTおよびDELETE SET DEFAULT参照アクション

  • 親表と子表が分散データベースの異なるノード上にある場合の参照整合性

  • CHECK制約に指定できる式では定義できない複雑なチェック制約

トリガーを使用した参照整合性

トリガーは、宣言がサポートされていないアクションを実行している場合にのみ使用します。

トリガーを使用して参照整合性を保持する場合は、親表にPRIMARY(またはUNIQUEKEY制約を宣言します。 同じデータベース内の親表と子表間の参照整合性を保持している場合は、子表にも外部キーを宣言できますが、子表の外部キーは無効にします。 子表のトリガーを無効にすることによって、対応するPRIMARY KEY制約が(CASCADEオプションでPRIMARY KEY制約を明示的に削除しないかぎり)削除されなくなります。

トリガーを使用して参照整合性を保持するには、次の操作を実行します。

  • 子表には、外部キーに挿入または更新される値を親キーの値と対応させるトリガーを1つ定義します。

  • 親表には、親キーで値が更新または削除された場合に、外部キーの値に対して必要な参照アクション(RESTRICTCASCADEまたはSET NULL)を実行する1つ以上のトリガーを定義します。親表への挿入にアクションは不要です(依存外部キーは存在しません)。

次のトピックでは、参照整合性の実行に必要なトリガーの例を示します。

次の項の例では、emp表とdept表の関係を使用します。一部のトリガーには、行をロックする文(SELECT FOR UPDATE)が含まれています。この操作は、行の処理時に同時実行性を保持するために必要です。

子表に対する外部キー・トリガー

次のトリガーは、INSERT文またはUPDATE文が外部キー値に影響する前に、対応する値が親キー内に確実に存在することを保証します。次の例に含まれている変更表例外によって、このトリガーをUPDATE_SET_DEFAULTトリガーおよびUPDATE_CASCADEトリガーとともに使用できるようになります。このトリガーを単独で使用する場合は、この例外を削除できます。

CREATE OR REPLACE TRIGGER Emp_dept_check
  BEFORE INSERT OR UPDATE OF Deptno ON emp
    FOR EACH ROW WHEN (new.Deptno IS NOT NULL)

  -- Before row is inserted or DEPTNO is updated in emp table,
  -- fire this trigger to verify that new foreign key value (DEPTNO)
  -- is present in dept table.
DECLARE
  Dummy              INTEGER;  -- Use for cursor fetch
  Invalid_department EXCEPTION;
  Valid_department   EXCEPTION;
  Mutating_table     EXCEPTION;
  PRAGMA EXCEPTION_INIT (Mutating_table, -4091);

  -- Cursor used to verify parent key value exists.
  -- If present, lock parent key's row so it cannot be deleted
  -- by another transaction until this transaction is
  -- committed or rolled back.
  CURSOR Dummy_cursor (Dn NUMBER) IS
    SELECT Deptno FROM dept
      WHERE Deptno = Dn
        FOR UPDATE OF Deptno;
BEGIN
  OPEN Dummy_cursor (:NEW.Deptno);
  FETCH Dummy_cursor INTO Dummy;

  -- Verify parent key.
  -- If not found, raise user-specified error number & message.
  -- If found, close cursor before allowing triggering statement to complete:
  IF Dummy_cursor%NOTFOUND THEN
    RAISE Invalid_department;
  ELSE
    RAISE valid_department;
  END IF;
  CLOSE Dummy_cursor;
EXCEPTION
  WHEN Invalid_department THEN
    CLOSE Dummy_cursor;
    Raise_application_error(-20000, 'Invalid Department'
      || ' Number' || TO_CHAR(:NEW.deptno));
  WHEN Valid_department THEN
    CLOSE Dummy_cursor;
  WHEN Mutating_table THEN
    NULL;
END;

親表に対するUPDATEおよびDELETE RESTRICTトリガー

次のトリガーをdept表に定義し、dept表の主キーに対してUPDATEおよびDELETE RESTRICT参照アクションを実行します。

CREATE OR REPLACE TRIGGER Dept_restrict
  BEFORE DELETE OR UPDATE OF Deptno ON dept
    FOR EACH ROW

-- Before row is deleted from dept or primary key (DEPTNO) of dept is updated,
-- check for dependent foreign key values in emp;
-- if any are found, roll back.

DECLARE
  Dummy                 INTEGER;  -- Use for cursor fetch
  Employees_present     EXCEPTION;
  employees_not_present EXCEPTION;

  -- Cursor used to check for dependent foreign key values.
  CURSOR Dummy_cursor (Dn NUMBER) IS
    SELECT Deptno FROM emp WHERE Deptno = Dn;

BEGIN
  OPEN Dummy_cursor (:OLD.Deptno);
  FETCH Dummy_cursor INTO Dummy;

  -- If dependent foreign key is found, raise user-specified
  -- error number and message. If not found, close cursor
  -- before allowing triggering statement to complete.

  IF Dummy_cursor%FOUND THEN
    RAISE Employees_present;     -- Dependent rows exist
  ELSE
    RAISE Employees_not_present; -- No dependent rows exist
  END IF;
  CLOSE Dummy_cursor;

EXCEPTION
  WHEN Employees_present THEN
    CLOSE Dummy_cursor;
    Raise_application_error(-20001, 'Employees Present in'
      || ' Department ' || TO_CHAR(:OLD.DEPTNO));
  WHEN Employees_not_present THEN
    CLOSE Dummy_cursor;
END;

注意:

このトリガーは、自己参照型の表(主キーまたは一意キーおよび外部キーが存在する表)では機能しません。また、このトリガーでは、(AがBを起動し、BがAを起動する)トリガーの循環は使用できません。

親表に対するUPDATEおよびDELETE SET NULLトリガー

次のトリガーをdept表に定義し、dept表の主キーに対してUPDATEおよびDELETE SET NULL参照アクションを実行します。

CREATE OR REPLACE TRIGGER Dept_set_null
  AFTER DELETE OR UPDATE OF Deptno ON dept
    FOR EACH ROW

-- Before row is deleted from dept or primary key (DEPTNO) of dept is updated,
-- set all corresponding dependent foreign key values in emp to NULL:

BEGIN
  IF UPDATING AND :OLD.Deptno != :NEW.Deptno OR DELETING THEN
    UPDATE emp SET emp.Deptno = NULL
      WHERE emp.Deptno = :OLD.Deptno;
   END IF;
END;

親表に対するDELETE CASCADEトリガー

dept表に対する次のトリガーは、dept表の主キーに対してDELETE CASCADE参照アクションを実行します。

CREATE OR REPLACE TRIGGER Dept_del_cascade
  AFTER DELETE ON dept
    FOR EACH ROW

-- Before row is deleted from dept,
-- delete all rows from emp table whose DEPTNO is same as
-- DEPTNO being deleted from dept table:

BEGIN
  DELETE FROM emp
    WHERE emp.Deptno = :OLD.Deptno;
END;

注意:

通常、DELETE CASCADEのコードは、更新および削除の両方の可能性を考慮して、UPDATE SET NULLまたはUPDATE SET DEFAULTのコードと組み合されます。

親表に対するUPDATE CASCADEトリガー

次のトリガーは、dept表の部門番号が更新された場合にその変更がemp表の依存外部キーに確実に伝播されることを保証します。

-- Generate sequence number to be used as flag
-- for determining if update occurred on column:
CREATE SEQUENCE Update_sequence
  INCREMENT BY 1 MAXVALUE 5000 CYCLE;

CREATE OR REPLACE PACKAGE Integritypackage AS
  Updateseq NUMBER;
END Integritypackage;

CREATE OR REPLACE PACKAGE BODY Integritypackage AS
END Integritypackage;

-- Create flag col:
ALTER TABLE emp ADD Update_id NUMBER;

CREATE OR REPLACE TRIGGER Dept_cascade1 BEFORE UPDATE OF Deptno ON dept
DECLARE
-- Before updating dept table (this is a statement trigger),
-- generate new sequence number
-- & assign it to public variable UPDATESEQ of
-- user-defined package named INTEGRITYPACKAGE:
BEGIN
  Integritypackage.Updateseq := Update_sequence.NEXTVAL;
END;

CREATE OR REPLACE TRIGGER Dept_cascade2
  AFTER DELETE OR UPDATE OF Deptno ON dept
    FOR EACH ROW

-- For each department number in dept that is updated,
-- cascade update to dependent foreign keys in emp table.
-- Cascade update only if child row was not already updated by this trigger:
BEGIN
  IF UPDATING THEN
    UPDATE emp
      SET Deptno = :NEW.Deptno,
        Update_id = Integritypackage.Updateseq   --from 1st
          WHERE emp.Deptno = :OLD.Deptno
            AND Update_id IS NULL;
            /* Only NULL if not updated by 3rd trigger
            fired by same triggering statement */
   END IF;
   IF DELETING THEN
     -- Before row is deleted from dept,
     -- delete all rows from emp table whose DEPTNO is same as
     -- DEPTNO being deleted from dept table:
     DELETE FROM emp
       WHERE emp.Deptno = :OLD.Deptno;
   END IF;
END;

CREATE OR REPLACE TRIGGER Dept_cascade3 AFTER UPDATE OF Deptno ON dept
BEGIN UPDATE emp
  SET Update_id = NULL
    WHERE Update_id = Integritypackage.Updateseq;
END;

注意:

このトリガーによってemp表が更新されるため、Emp_dept_checkトリガー(有効になっている場合)も起動されます。結果として生成される変更表エラーは、Emp_dept_checkトリガーによってトラップされます。エラーのトラップが必要なトリガーは、使用環境で常に正常に動作することを保証できるように、慎重にテストしてください。

複雑なチェック制約に対するトリガー

トリガーは、参照整合性以外の整合性規則も実行できます。たとえば、次のトリガーは、トリガーを起動する文の実行を許可する前に、複雑なチェックを実行します。


注意:

この例を実行するには、次のデータ構造を設定する必要がある場合があります。
CREATE OR REPLACE TABLE Salgrade (
  Grade               NUMBER,
  Losal               NUMBER,
  Hisal               NUMBER,
  Job_classification  NUMBER);

CREATE OR REPLACE TRIGGER Salary_check
  BEFORE INSERT OR UPDATE OF Sal, Job ON Emp99
    FOR EACH ROW
DECLARE
  Minsal               NUMBER;
  Maxsal               NUMBER;
  Salary_out_of_range  EXCEPTION;

BEGIN
/* Retrieve minimum & maximum salary for employee's new job classification
  from SALGRADE table into MINSAL and MAXSAL: */

  SELECT Minsal, Maxsal INTO Minsal, Maxsal
    FROM Salgrade
      WHERE Job_classification = :NEW.Job;

/* If employee's new salary is less than or greater than
   job classification's limits, raise exception.
   Exception message is returned and pending INSERT or UPDATE statement
   that fired the trigger is rolled back:*/

  IF (:NEW.Sal < Minsal OR :NEW.Sal > Maxsal) THEN
    RAISE Salary_out_of_range;
  END IF;
EXCEPTION
  WHEN Salary_out_of_range THEN
    Raise_application_error (-20300,
      'Salary '||TO_CHAR(:NEW.Sal)||' out of range for '
      ||'job classification '||:NEW.Job
      ||' for employee '||:NEW.Ename);
  WHEN NO_DATA_FOUND THEN
    Raise_application_error(-20322,
      'Invalid Job Classification '
      ||:NEW.Job_classification);
END;

複雑なセキュリティ認可およびトリガー

トリガーは、表データに対する複雑なセキュリティ認可の実行によく使用されます。 トリガーは、データベースで提供されるデータベース・セキュリティ機能では定義できない複雑なセキュリティ認可の実行にのみ使用します。たとえば、トリガーを使用して、週末、休日および休業時間に、emp表の給与データを更新できないようにすることができます。

複雑なセキュリティ認可の実行にトリガーを使用する場合は、BEFORE文トリガーを使用することをお薦めします。BEFORE文トリガーを使用すると、次のメリットがあります。

  • トリガーを起動する文の実行が許可される前にセキュリティ・チェックが実行されるため、不正な文による無駄な作業は行われません。

  • セキュリティ・チェックは、トリガーを起動する文に影響される各行に対してではなく、トリガーを起動する文に対して1回のみ実施されます。

次に、セキュリティを施行するために使用するトリガーの例を示します。


注意:

この例を実行するには、次のデータ構造を設定する必要がある場合があります。
CREATE TABLE Company_holidays (Day DATE);

CREATE OR REPLACE TRIGGER Emp_permit_changes
  BEFORE INSERT OR DELETE OR UPDATE ON Emp99
DECLARE
  Dummy             INTEGER;
  Not_on_weekends   EXCEPTION;
  Not_on_holidays   EXCEPTION;
  Non_working_hours EXCEPTION;
BEGIN
   /* Check for weekends: */
   IF (TO_CHAR(Sysdate, 'DY') = 'SAT' OR
     TO_CHAR(Sysdate, 'DY') = 'SUN') THEN
       RAISE Not_on_weekends;
   END IF;

   /* Check for company holidays: */
   SELECT COUNT(*) INTO Dummy FROM Company_holidays
     WHERE TRUNC(Day) = TRUNC(Sysdate); -- Discard time parts of dates
   IF dummy > 0 THEN
     RAISE Not_on_holidays;
   END IF;

  /* Check for work hours (8am to 6pm): */
  IF (TO_CHAR(Sysdate, 'HH24') < 8 OR
    TO_CHAR(Sysdate, 'HH24') > 18) THEN
      RAISE Non_working_hours;
  END IF;
EXCEPTION
  WHEN Not_on_weekends THEN
    Raise_application_error(-20324,'Might not change '
      ||'employee table during the weekend');
  WHEN Not_on_holidays THEN
    Raise_application_error(-20325,'Might not change '
      ||'employee table during a holiday');
  WHEN Non_working_hours THEN
    Raise_application_error(-20326,'Might not change '
     ||'emp table during nonworking hours');
END;

参照:

データベース・セキュリティ機能の詳細は『Oracle Databaseセキュリティ・ガイド』を参照してください。

透過的なイベント・ロギングおよびトリガー

特定のイベントに続いて、関連する変更をデータベースで透過的に実行する場合、トリガーは非常に有効です。

REORDERトリガーの例は、一定の条件が満たされると、必要に応じて部品を再注文するトリガーを示しています。(つまり、トリガーを起動する文が入力され、PARTS_ON_HAND値がREORDER_POINT値より小さい場合です。)

導出列値およびトリガー

トリガーは、INSERT文またはUPDATE文で指定される値に基づいて、列の値を自動的に導出できます。このタイプのトリガーは、同じ行内の他の列値に依存する特定の列に値を強制的に設定する場合に有効です。次に示す理由から、このタイプの操作を実行するにはBEFORE行トリガーが必要です。

  • トリガーを起動する文で導出値を使用できるようにするために、INSERTまたはUPDATEが発生する前に依存値を導出する必要があります。

  • トリガーを起動するINSERT文またはUPDATE文によって影響される各行に対して、トリガーを起動する必要があります。

次に、行が挿入または更新されるたびに、表の新しい列値を導出するトリガーの使用方法の例を示します。


注意:

この例を実行するには、次のデータ構造を設定する必要がある場合があります。
ALTER TABLE Emp99 ADD(
   Uppername   VARCHAR2(20),
   Soundexname VARCHAR2(20));

CREATE OR REPLACE TRIGGER Derived
BEFORE INSERT OR UPDATE OF Ename ON Emp99

/* Before updating the ENAME field, derive the values for
   the UPPERNAME and SOUNDEXNAME fields. Restrict users
   from updating these fields directly: */
FOR EACH ROW
BEGIN
   :NEW.Uppername := UPPER(:NEW.Ename);
   :NEW.Soundexname := SOUNDEX(:NEW.Ename);
END;

トリガーを使用した複合更新可能ビューの作成

ビューは、表データに対して論理ウィンドウを提供するための優れたメカニズムです。ただし、ビューの問合せが複雑になると、ビューに対するDMLから基礎となる表に対するDMLへの変換を、システムで暗黙的に実行できなくなります。この問題の解決には、INSTEAD OFトリガーが有効です。このトリガーは、ビューに対して定義でき、実際のDMLのかわりに起動されます。

書籍が書名の順に配置されているライブラリ・システムについて考えてみます。このライブラリは、一連の書籍型オブジェクトで構成されています。次の例は、このスキーマについて説明しています。

CREATE OR REPLACE TYPE Book_t AS OBJECT
(
   Booknum   NUMBER,
   Title     VARCHAR2(20),
   Author    VARCHAR2(20),
   Available CHAR(1)
);
CREATE OR REPLACE TYPE Book_list_t AS TABLE OF Book_t;

関係スキーマに次の表が存在しているとします。

Table Book_table (Booknum, Section, Title, Author, Available)
Booknum Section Title Author Available
121001 Classic Iliad Homer Y
121002 Novel Gone with the Wind Mitchell M N

このライブラリは、library_table(section)で構成されています。

Section
Geography
Classic

これらの表に対して複合ビューを定義し、セクションおよび各セクション内の一連の書籍を示すライブラリの論理ビューを作成できます。

CREATE OR REPLACE VIEW Library_view AS
SELECT i.Section, CAST (MULTISET (
   SELECT b.Booknum, b.Title, b.Author, b.Available
   FROM Book_table b
   WHERE b.Section = i.Section) AS Book_list_t) BOOKLIST
FROM Library_table i;

このビューに対してINSTEAD OFトリガーを定義することによって、このビューを更新可能にします。

CREATE OR REPLACE TRIGGER Library_trigger INSTEAD OF INSERT ON Library_view FOR EACH ROW
   Bookvar BOOK_T;
   i       INTEGER;
BEGIN
   INSERT INTO Library_table VALUES (:NEW.Section);
   FOR i IN 1..:NEW.Booklist.COUNT LOOP
      Bookvar := Booklist(i);
      INSERT INTO book_table
         VALUES ( Bookvar.booknum, :NEW.Section, Bookvar.Title, Bookvar.Author, bookvar.Available);
   END LOOP;
END;
/

library_viewは更新可能なビューであり、このビューに対するすべてのINSERTは、自動的に起動されるトリガーによって処理されます。次に例を示します。

INSERT INTO Library_view VALUES ('History', book_list_t(book_t(121330, 'Alexander', 'Mirth', 'Y');

同様に、ネストした表booklistに対してトリガーを定義して、このネストした表の要素の変更を処理することもできます。

トリガーを使用したファイングレイン・アクセス制御

LOGONトリガーを使用すると、アプリケーション・コンテキストに関連付けられたパッケージを実行できます。 アプリケーション・コンテキストによって、データベースにログインしているユーザーのセッション関連の情報が取得されます。 アプリケーションから、ユーザーのセッション情報に基づいて、ユーザーが持つアクセス権を制御できます。


注意:

ユーザーによるファイアウォールの外側からのログインや勤務時間外のログインの防止などの特別なログオン要件がある場合は、LOGONトリガーではなくOracle Database Vaultを使用することを検討してください。 Oracle Database Vaultを使用すると、ユーザーのアクセスを厳密に制御する独自の規則を作成できます。


参照:

  • LOGONトリガーを作成してデータベース・セッションのアプリケーション・コンテキスト・パッケージを実行する方法の詳細は、『Oracle Databaseセキュリティ・ガイド』を参照してください。

  • Oracle Database Vaultの詳細は、『Oracle Database Vault管理者ガイド』を参照してください。


トリガーを介したデータベース・イベントに対する応答


注意:

このトピックの内容は、単純なトリガーにのみ適用されます。

データベース・イベントをパブリッシュすることによって、アプリケーションで、他のアプリケーションからのメッセージをサブスクライブする場合と同様にデータベース・イベントをサブスクライブできます。データベース・イベントのパブリケーション・フレームワークには、次の機能が含まれています。

トリガーを作成することによって、イベント発生時に実行するサブプログラムを指定できます。DMLイベントは表でサポートされ、データベース・イベントはDATABASEおよびSCHEMAでサポートされます。ALTER TRIGGER文を使用してトリガーを有効および無効にすることによって、通知を有効および無効にできます。

この機能は、アドバンスト・キューイング・エンジンに統合されています。パブリッシュ/サブスクライブ・アプリケーションではDBMS_AQ.ENQUEUEプロシージャが使用され、他のアプリケーション(カートリッジなど)ではコールアウトが使用されます。


参照:

  • ALTER TRIGGER文

  • パブリッシュ済のイベントのサブスクライブ方法の詳細は、『Oracle Streamsアドバンスト・キューイング・ユーザーズ・ガイド』を参照してください。


ここでのトピック:

トリガーを介したイベントのパブリッシュ方法

データベースによってイベントが検出されると、トリガー・メカニズムがトリガー内に指定されているアクションを実行します。このアクションの一部として、サブスクライバが通知を受信できるようにイベントをキューにパブリッシュできます。イベントをパブリッシュするには、DBMS_AQパッケージを使用します。


注意:

データベースは、システム定義のイベントのみ検出できます。独自のイベントは定義できません。

データベースは、イベントを検出すると、そのイベントに対して使用可能なすべてのトリガーを起動します。これには次の例外があります。

  • トリガーがトリガーを起動するイベントのターゲットである場合、このトリガーは起動されません。

    たとえば、すべてのDROPイベントのトリガーは、トリガー自体が削除される場合は起動されません。

  • 変更されたトリガーが、トリガーを起動するイベントと同じトランザクション内でコミットされなかった場合、このトリガーは起動されません。

    たとえば、システム・トリガー内の再帰DDLによってトリガーが変更される場合があります。これによって、変更されたトリガーは同じトランザクション内でイベントによって起動されなくなります。


参照:

DBMS_AQパッケージの詳細は『Oracle Database PL/SQLパッケージ・プロシージャおよびタイプ・リファレンス』を参照してください。

パブリケーション・コンテキスト

イベントがパブリッシュされると、パラメータ・リストに指定されている特定の実行時コンテキストおよび属性がコールアウト・サブプログラムに渡されます。イベント属性ファンクションと呼ばれる一連のファンクションが提供されています。


参照:

イベント固有の属性の詳細は、「イベント属性ファンクション」を参照してください。

サポートされている各データベース・イベントに対して、イベント固有の属性を指定および事前定義できます。パラメータ・リストには、他の単純な式とともにこの属性のいずれかを選択できます。コールアウトの場合、これらはIN引数として渡されます。

エラーの処理

すべてのイベントに対するパブリケーション・コールアウト・ファンクションからの戻りステータスは、無視されます。たとえば、SHUTDOWNイベントの場合、データベースは戻りステータスに対して何も実行できません。

実行モデル

従来、1つのトリガーの定義者として複数のトリガーが実行されてきました。イベントのトリガー・アクションは、アクションの定義者として(コールアウト内のパッケージまたはファンクションの定義者、またはキュー内のトリガーの所有者として)実行されます。トリガーの所有者には基礎となるキュー、パッケージまたはサブプログラムに対するEXECUTE権限が必要なため、このアクションには一貫性があります。

イベント属性ファンクション

データベースによってトリガーが起動されると、トリガーを起動したイベントに関する特定の属性を取り出すことができます。ファンクション・コールを使用して、各属性を取り出すことができます。表9-3に、システム定義のイベント属性を示します。


注意:

  • トリガー・ディクショナリ・オブジェクトは、パブリッシュされるイベントに関するメタデータおよびそれに対応する属性を保持します。

  • 以前のリリースでは、これらのファンクションはSYSパッケージを介してアクセスされていました。 名前がora_で始まるこれらのパブリック・シノニムを使用することをお薦めします。

  • ora_name_list_tは、DBMS_STANDARDパッケージ内で次のように定義されます。

    TYPE ora_name_list_t IS TABLE OF VARCHAR2(64);
    

表9-3 システム定義のイベント属性

属性 説明
ora_client_ip_address
VARCHAR2

基礎となるプロトコルがTCP/IPの場合、LOGONイベントでクライアントのIPアドレスを戻します。

DECLARE
  v_addr VARCHAR2(11);
BEGIN
  IF (ora_sysevent = 'LOGON') THEN
    v_addr := ora_client_ip_address;
  END IF;
END;
ora_database_name
VARCHAR2(50)

データベース名。

DECLARE
  v_db_name VARCHAR2(50);
BEGIN
  v_db_name := ora_database_name;
END;
ora_des_encrypted_password
VARCHAR2

作成または変更されるユーザーのDES暗号化パスワード。

IF (ora_dict_obj_type = 'USER') THEN
  INSERT INTO event_table
    VALUES (ora_des_encrypted_password);
END IF;
ora_dict_obj_name
VARCHAR(30)

DDL操作が発生したディクショナリ・オブジェクトの名前。

INSERT INTO event_table
  VALUES ('Changed object is ' ||
           ora_dict_obj_name);
ora_dict_obj_name_list
(name_list OUT ora_name_list_t)
PLS_INTEGER

このイベントで変更されるオブジェクトのオブジェクト名のリストを戻します。

DECLARE
name_list   DBMS_STANDARD.ora_name_list_t;
number_modified  PLS_INTEGER;
BEGIN
IF (ora_sysevent='ASSOCIATE STATISTICS')
  THEN number_modified :=
       ora_dict_obj_name_list(name_list);
END IF;
END;
ora_dict_obj_owner
VARCHAR(30)

DDL操作が発生したディクショナリ・オブジェクトの所有者。

INSERT INTO event_table
  VALUES ('object owner is' ||
           ora_dict_obj_owner);
ora_dict_obj_owner_list
(owner_list OUT ora_name_list_t)
PLS_INTEGER

このイベントで変更されるオブジェクトのオブジェクト所有者のリストを戻します。

DECLARE
owner_list
   DBMS_STANDARD.ora_name_list_t;
number_modified   PLS_INTEGER;
BEGIN
IF (ora_sysevent='ASSOCIATE STATISTICS')
  THEN number_modified :=
       ora_dict_obj_name_list(owner_list);
END IF;
END;
ora_dict_obj_type
VARCHAR(20)

DDL操作が発生したディクショナリ・オブジェクトのタイプ。

INSERT INTO event_table
  VALUES ('This object is a ' ||
           ora_dict_obj_type);
ora_grantee
(user_list OUT ora_name_list_t)
PLS_INTEGER

OUTパラメータには権限付与イベントの権限受領者を戻し、戻り値には権限受領者の数を戻します。

DECLARE
user_list   DBMS_STANDARD.ora_name_list_t;
number_of_grantees   PLS_INTEGER;
BEGIN
IF (ora_sysevent = 'GRANT') THEN
number_of_grantees :=
 ora_grantee(user_list);
END IF;
END;
ora_instance_num
NUMBER

インスタンス番号。

IF (ora_instance_num = 1) THEN
  INSERT INTO event_table VALUES ('1');
END IF;
ora_is_alter_column
(column_name IN VARCHAR2)
BOOLEAN

指定した列が変更されている場合は、TRUEを戻します。

IF (ora_sysevent = 'ALTER' AND
    ora_dict_obj_type = 'TABLE') THEN
 alter_column := ora_is_alter_column('C');
END IF;
ora_is_creating_nested_table
BOOLEAN

現行のイベントによってネストした表が作成されている場合は、TRUEを戻します。

IF (ora_sysevent = 'CREATE' and
    ora_dict_obj_type = 'TABLE' and
    ora_is_creating_nested_table) THEN
  INSERT INTO event_table
    VALUES ('A nested table is created');
END IF;
ora_is_drop_column
(column_name IN VARCHAR2)
BOOLEAN

指定した列が削除されている場合は、TRUEを戻します。

IF (ora_sysevent = 'ALTER' AND
    ora_dict_obj_type = 'TABLE') THEN
 drop_column := ora_is_drop_column('C');
END IF;
ora_is_servererror
BOOLEAN

指定したエラーがエラー・スタック上にある場合はTRUE、ない場合はFALSEを戻します。

IF ora_is_servererror(error_number) THEN
 INSERT INTO event_table
   VALUES ('Server error!!');
END IF;
ora_login_user
VARCHAR2(30)

ログイン・ユーザー名。

SELECT ora_login_user
FROM DUAL;
ora_partition_pos
PLS_INTEGER

CREATE TABLEINSTEAD OFトリガーでは、PARTITION句を挿入できるSQLテキスト内の位置。

-- Retrieve ora_sql_txt into
-- sql_text variable first.
v_n := ora_partition_pos;
v_new_stmt := SUBSTR(sql_text,1,v_n - 1)
         || ' ' || my_partition_clause
         || ' ' || SUBSTR(sql_text, v_n));
ora_privilege_list
(privilege_list
 OUT ora_name_list_t)
PLS_INTEGER

権限受領者によって付与された権限のリストまたは権限取消し対象者から取り消された権限のリストをOUTパラメータに戻します。戻り値には権限の数を戻します。

DECLARE
privelege_list
        DBMS_STANDARD.ora_name_list_t;
number_of_privileges   PLS_INTEGER;
BEGIN
IF (ora_sysevent = 'GRANT' OR
    ora_sysevent = 'REVOKE') THEN
  number_of_privileges :=
    ora_privilege_list(privilege_list);
END IF;
END;
ora_revokee
(user_list OUT ora_name_list_t)
PLS_INTEGER

取消しイベントの権限取消し対象者をOUTパラメータに戻します。戻り値に権限取消し対象者の数を戻します。

DECLARE
user_list   DBMS_STANDARD.ora_name_list_t;
number_of_users   PLS_INTEGER;
BEGIN
IF (ora_sysevent = 'REVOKE') THEN
number_of_users := ora_revokee(user_list);
END IF;
END;
ora_server_error
NUMBER

(スタックの最上位を1として)位置を指定すると、エラー・スタック上のその位置のエラー番号を戻します。

INSERT INTO event_table
  VALUES ('top stack error ' ||
           ora_server_error(1));
ora_server_error_depth
PLS_INTEGER

エラー・スタック上のエラー・メッセージの合計数を戻します。

n := ora_server_error_depth;
-- This value is used with other functions -- such as ora_server_error
ora_server_error_msg
(position in pls_integer)
VARCHAR2

(スタックの最上位を1として)位置を指定すると、エラー・スタック上のその位置のエラー・メッセージを戻します。

INSERT INTO event_table
  VALUES ('top stack error message' ||
           ora_server_error_msg(1));
ora_server_error_num_params
(position in pls_integer)
PLS_INTEGER

(スタックの最上位を1として)位置を指定すると、%sなどの書式を使用してエラー・メッセージに置き換えられた文字列の数を戻します。

n := ora_server_error_num_params(1);
ora_server_error_param
(position in pls_integer,
 param in pls_integer)
VARCHAR2

(スタックの最上位を1として)位置およびパラメータ番号を指定すると、エラー・メッセージ内の一致する置換値(%s%dなど)を戻します。

-- For example, the second %s in a
-- message: "Expected %s, found %s"
param := ora_server_error_param(1,2);
ora_sql_txt
(sql_text out ora_name_list_t)
PLS_INTEGER

トリガーを起動する文のSQLテキストをOUTパラメータに戻します。この文が長い場合、複数のPL/SQL表要素に分割されます。ファンクションの戻り値は、PL/SQL表内の要素数を示します。

--...
-- Create table event_table
create table event_table (col
 VARCHAR2(2030));
--...
DECLARE
sql_text   DBMS_STANDARD.ora_name_list_t;
n   PLS_INTEGER;
v_stmt VARCHAR2(2000);
BEGIN
n := ora_sql_txt(sql_text);

FOR i IN 1..n LOOP
v_stmt := v_stmt || sql_text(i);
END LOOP;

INSERT INTO event_table VALUES ('text of
 triggering statement: ' || v_stmt);
END;
ora_sysevent
VARCHAR2(20)

トリガーを起動するデータベース・イベント。イベント名は、構文内の名前と同じです。

INSERT INTO event_table
  VALUES (ora_sysevent);
ora_with_grant_option
BOOLEAN

権限オプションとともに権限が付与されている場合は、TRUEを戻します。

IF (ora_sysevent = 'GRANT' and
    ora_with_grant_option = TRUE) THEN
  INSERT INTO event_table
    VALUES ('with grant option');
END IF;
space_error_info
(error_number OUT NUMBER,
 error_type OUT VARCHAR2,
 object_owner OUT VARCHAR2,
 table_space_name OUT
  VARCHAR2,
 object_name OUT VARCHAR2,
 sub_object_name OUT VARCHAR2)
BOOLEAN

エラーが領域不足状態に関連している場合はTRUEを戻し、エラーの原因となったオブジェクトに関する情報をOUTパラメータに示します。

IF (space_error_info(eno,typ,owner,ts,obj,
                     subobj) = TRUE) THEN
  DBMS_OUTPUT.PUT_LINE('The object '|| obj
               || ' owned by ' || owner ||
               ' has run out of space.');
END IF;

データベース・イベント

データベース・イベントは、個々の表または行ではなく、インスタンスまたはスキーマ全体に関連しています。起動イベントおよび停止イベントに関連付けられているトリガーは、データベース・インスタンスに対して定義する必要があります。エラー発生イベントおよび一時停止イベントに関連付けられているトリガーは、データベース・インスタンスまたは特定のスキーマのいずれかに対して定義できます。

表9-4 データベース・イベント

イベント トリガーが起動されるタイミング 条件 制限 トランザクション 属性ファンクション
STARTUP

データベースのオープン時。

なし

トリガーではデータベース操作は実行できません。

戻りステータスは無視されます。

トリガーの起動後、別のトランザクションを開始してコミットします。

ora_sysevent
ora_login_user
ora_instance_num
ora_database_name
SHUTDOWN

サーバーでインスタンスの停止が開始される直前。

これによって、カートリッジを完全に停止できます。インスタンスの異常停止の場合、このトリガーは起動されない場合があります。

なし

トリガーではデータベース操作は実行できません。

戻りステータスは無視されます。

トリガーの起動後、別のトランザクションを開始してコミットします。

ora_sysevent
ora_login_user
ora_instance_num
ora_database_name
DB_ROLE_CHANGE

ロールの変更後最初のデータベースのオープン時。

なし

戻りステータスは無視されます。

トリガーの起動後、別のトランザクションを開始してコミットします。

ora_sysevent
ora_login_user
ora_instance_num
ora_database_name
SERVERERROR

enoエラーの発生時。条件が指定されていない場合、このトリガーはエラーが発生するたびに起動されます。

ORA-1034、ORA-1403、ORA-1422、ORA-1423およびORA-4030が発生した場合、トリガーは起動されません。本当のエラーではないか、またはエラーが重大すぎて処理を続行できないためです。 また、ORA-18およびORA-20の場合もトリガーは起動されません。プロセスで、データベースに接続してエラーを記録できないためです。

ERRNO = eno

エラーによって異なります。

戻りステータスは無視されます。

トリガーの起動後、別のトランザクションを開始してコミットします。

ora_sysevent
ora_login_user
ora_instance_num
ora_database_name
ora_server_error
ora_is_servererror
space_error_info

クライアント・イベント

クライアント・イベントは、ユーザーのログオン/ログオフ、DMLおよびDDLの操作に関連するイベントです。

LOGONイベントおよびLOGOFFイベントでは、UIDおよびUSERに単純な条件を使用できます。他のすべてのイベントでは、UIDUSERなどのファンクションのみでなく、オブジェクトの型および名前に単純な条件を使用できます。

LOGONイベントは、トリガーの起動後、別のトランザクションを開始してコミットします。他のすべてのイベントは、既存のユーザー・トランザクションでトリガーを起動します。

LOGONイベントおよびLOGOFFイベントは、すべてのオブジェクトに対して操作を実行できます。他のすべてのイベントの場合、対応するトリガーは、対象のイベントを生成するオブジェクトに対してDROPALTERなどのDDL操作を実行できません。

これらのトリガー内で実行可能なDDLは、表の変更、表の作成、表の削除、トリガーの作成および操作のコンパイルです。

イベント・トリガーがDDL操作(CREATE TRIGGERなど)のターゲットになった場合、同じトランザクション中に後でこのトリガーを起動することはできません。

表9-5 クライアント・イベント

イベント トリガーが起動されるタイミング 属性ファンクション
BEFORE ALTER

AFTER ALTER

カタログ・オブジェクトの変更時。

ora_sysevent
ora_login_user
ora_instance_num
ora_database_name
ora_dict_obj_type
ora_dict_obj_name
ora_dict_obj_owner
ora_des_encrypted_password
 (for ALTER USER events)
ora_is_alter_column
 (for ALTER TABLE events)
ora_is_drop_column
 (for ALTER TABLE events)
BEFORE DROP

AFTER DROP

カタログ・オブジェクトの削除時。

ora_sysevent
ora_login_user
ora_instance_num
ora_database_name
ora_dict_obj_type
ora_dict_obj_name
ora_dict_obj_owner
BEFORE ANALYZE

AFTER ANALYZE

分析文の発行時。

ora_sysevent
ora_login_user
ora_instance_num
ora_database_name
ora_dict_obj_name
ora_dict_obj_type
ora_dict_obj_owner
BEFORE ASSOCIATE STATISTICS

AFTER ASSOCIATE STATISTICS

関連統計文の発行時。

ora_sysevent
ora_login_user
ora_instance_num
ora_database_name
ora_dict_obj_name
ora_dict_obj_type
ora_dict_obj_owner
ora_dict_obj_name_list
ora_dict_obj_owner_list
BEFORE AUDIT
AFTER AUDIT

BEFORE NOAUDIT
AFTER NOAUDIT

監査または非監査文の発行時。

ora_sysevent
ora_login_user
ora_instance_num
ora_database_name
BEFORE COMMENT

AFTER COMMENT

オブジェクトのコメント化時。

ora_sysevent
ora_login_user
ora_instance_num
ora_database_name
ora_dict_obj_name
ora_dict_obj_type
ora_dict_obj_owner
BEFORE CREATE

AFTER CREATE

カタログ・オブジェクトの作成時。

ora_sysevent
ora_login_user
ora_instance_num
ora_database_name
ora_dict_obj_type
ora_dict_obj_name
ora_dict_obj_owner
ora_is_creating_nested_table
 (for CREATE TABLE events)
BEFORE DDL

AFTER DDL

ほとんどのSQL DDL文の発行時。PL/SQLサブプログラム・インタフェースを介して発行されたALTER DATABASECREATE CONTROLFILECREATE DATABASEおよびDDL(アドバンスト・キューの作成など)に対しては起動されません。

ora_sysevent
ora_login_user
ora_instance_num
ora_database_name
ora_dict_obj_name
ora_dict_obj_type
ora_dict_obj_owner
BEFORE DISASSOCIATE STATISTICS

AFTER DISASSOCIATE STATISTICS

非関連統計文の発行時。

ora_sysevent
ora_login_user
ora_instance_num
ora_database_name
ora_dict_obj_name
ora_dict_obj_type
ora_dict_obj_owner
ora_dict_obj_name_list
ora_dict_obj_owner_list
BEFORE GRANT

AFTER GRANT

権限付与文の発行時。

ora_sysevent
ora_login_user
ora_instance_num
ora_database_name
ora_dict_obj_name
ora_dict_obj_type
ora_dict_obj_owner
ora_grantee
ora_with_grant_option
ora_privileges
BEFORE LOGOFF

ユーザー・ログオフの開始時。

ora_sysevent
ora_login_user
ora_instance_num
ora_database_name
AFTER LOGON

ユーザーが正常にログオンした後。

ora_sysevent
ora_login_user
ora_instance_num
ora_database_name
ora_client_ip_address
BEFORE RENAME

AFTER RENAME

名前変更文の発行時。

ora_sysevent
ora_login_user
ora_instance_num
ora_database_name
ora_dict_obj_name
ora_dict_obj_owner
ora_dict_obj_type
BEFORE REVOKE

AFTER REVOKE

取消し文の発行時

ora_sysevent
ora_login_user
ora_instance_num
ora_database_name
ora_dict_obj_name
ora_dict_obj_type
ora_dict_obj_owner
ora_revokee
ora_privileges
AFTER SUSPEND

領域不足状態のためSQL文が一時停止された後。このトリガーは、文を再開できるようにこの状態を修正する必要があります。

ora_sysevent
ora_login_user
ora_instance_num
ora_database_name
ora_server_error
ora_is_servererror
space_error_info
BEFORE TRUNCATE

AFTER TRUNCATE

オブジェクトの切捨て時。

ora_sysevent
ora_login_user
ora_instance_num
ora_database_name
ora_dict_obj_name
ora_dict_obj_type
ora_dict_obj_owner