| Oracle Database アプリケーション開発者ガイド-基礎編 10gリリース2(10.2) B19248-02 |
|
トリガーとは、データベース内に格納され、何かが発生したときに暗黙的に実行つまり起動されるプロシージャです。
これまでトリガーは、表またはビューに対してINSERT、UPDATEまたはDELETEが発生したときのPL/SQLブロックの実行をサポートしてきました。トリガーは、DATABASEおよびSCHEMAに対するシステム・イベントおよびその他のデータ・イベントをサポートしています。Oracle Databaseでは、PL/SQLプロシージャまたはJavaプロシージャの実行もサポートします。
この章では、DMLトリガー、INSTEAD OFトリガーおよびシステム・トリガー(DATABASEおよびSCHEMAに対するトリガー)について説明します。内容は次のとおりです。
トリガーを設計するときは、次のガイドラインを使用してください。
Emp_tab表に対してUPDATE文を発行するAFTER UPDATE文トリガーをEmp_tab表に作成すると、このトリガーはメモリー不足になるまで再帰的に起動し続けます。
DATABASEに対するトリガーは、慎重に使用してください。このようなトリガーは、トリガーの対象イベントが発生するたびに、すべてのユーザーに対して実行されます。
トリガーは、CREATE TRIGGER文を使用して作成します。この文は、SQL*PlusやEnterprise Managerなどの対話型ツールで使用できます。対話型ツールを使用する場合、CREATE TRIGGER文をアクティブにするには、最終行にスラッシュ(/)を1つ付けます。
次の文は、Emp_tab表に対するトリガーを作成します。
CREATE OR REPLACE TRIGGER Print_salary_changes BEFORE DELETE OR INSERT OR UPDATE ON Emp_tab 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操作(INSERT文、UPDATE文およびDELETE文)が表に実行されると、トリガーが起動されます。トリガーを起動する操作の組合せは選択できます。
トリガーは、BEFOREキーワードを使用するため、表に入る前に新しい値にアクセスできます。また、:NEW.column_nameに割り当てることによって、簡単に修正されたエラーがある場合は、その値を変更できます。トリガーは、初期変更が適用され、表が一貫性のある状態に戻った後にのみ同じ表の問合せまたは変更を実行できるため、トリガーでこれらの操作をする場合は、AFTERキーワードを使用します。
トリガーは、FOR EACH ROW句を使用するため、複数行の更新または削除時などに複数回実行される場合があります。操作が発生したという事実のみを記録し、各行のデータを調べない場合は、この句を省略します。
トリガーの作成後に、次のSQL文を入力します。
UPDATE Emp_tab SET sal = sal + 500.00 WHERE deptno = 10;
このSQL文により、更新される行ごとにトリガーが1回起動され、各行に対して新しい給与、古い給与およびその差異が出力されます。
PL/SQLブロックにエラーがあると、CREATE文(またはCREATE OR REPLACE文)は正常に実行されません。
次の項では、トリガーの各要素の指定方法を説明します。
トリガーは、ストアドPL/SQLブロックか、あるいは表、ビュー、スキーマまたはデータベース自体に関連付けられるPL/SQL、CまたはJavaのプロシージャです。Oracle Databaseでは、指定されたイベントが発生したときに、トリガーを自動的に実行します。イベントはシステム・イベントか、または表に対して発行されるDML文の形態をとります。
トリガーは次のいずれかです。
INSTEAD OFトリガー
DATABASEまたはSCHEMAに対するシステム・トリガー: DATABASEの場合は、トリガーはイベントごとにすべてのユーザーに対して起動されます。SCHEMAの場合は、トリガーはイベントごとに特定ユーザーに対して起動されます。 次のいずれかに対して起動されるトリガーを作成できます。
DELETE、INSERT、UPDATE)
CREATE、ALTER、DROP)
SERVERERROR、LOGON、LOGOFF、STARTUP、SHUTDOWN)
トリガーが起動されるときに、イベント固有の特定の属性を取得できます。
DATABASEに対してトリガーを作成するということは、トリガーになるイベントがユーザーの有効範囲の外にある(たとえば、データベースのSTARTUPおよびSHUTDOWN)ことを意味し、そのトリガーはすべてのユーザーに適用されます(たとえば、LOGONイベントに対してDBAにより作成されるトリガー)。
SCHEMAに対してトリガーを作成するということは、トリガーが現行のユーザーのスキーマ内に作成され、そのユーザーに対してのみ起動されることを意味します。
それぞれのトリガーに関して、DMLおよびシステム・イベントに対して発行を指定できます。
トリガーの名前は、同一スキーマ内の他のトリガーに対して一意である必要があります。他のスキーマ・オブジェクト(表、ビュー、プロシージャなど)名とは重複してもかまいません。たとえば、表とトリガーに同じ名前を付けることもできます(ただし、間違えやすいため、違う名前を付けることをお薦めします)。
トリガーは、トリガーを実行する文に基づいて起動されます。トリガーを実行する文では次のものを指定します。
DELETE、INSERTおよびUPDATEがあります。これらのオプションのうちのいずれか、またはすべてをトリガーを実行する文の仕様部に組み込むことができます。
DATABASEまたはSCHEMA。たとえば、PRINT_SALARY_CHANGESトリガーは、Emp_tab表に対してDELETE、INSERTまたはUPDATEのいずれかが実行されたときに起動されます。次のいずれかの文によって、前述の例で使用されているPRINT_SALARY_CHANGESトリガーが実行されます。
DELETE FROM Emp_tab; INSERT INTO Emp_tab VALUES ( ... ); INSERT INTO Emp_tab SELECT ... FROM ... ; UPDATE Emp_tab SET ... ;
INSERTトリガーは、SQL*Loaderによる通常のロード中に起動されます。(ダイレクト・ロードの場合、トリガーはロードの前に使用禁止になります。)
IMPコマンドのIGNOREパラメータは、インポート中にトリガーを起動するかどうかを決定します。
IGNORE=N(デフォルト)および表がすでに存在する場合は、インポートでは表は変更されず、既存のトリガーは起動されません。
IGNORE=Yの場合は、インポートによって行が既存の表にロードされます。既存のトリガーがすべて起動され、インポートされたデータを含めるために索引が更新されます。
UPDATE文が列のリストを含む場合があります。トリガーを実行する文に列リストが含まれる場合、トリガーは指定された列の1つが更新される場合にのみ起動されます。トリガーを実行する文で列リストが省略された場合、トリガーは関連付けられた表のいずれかの列が更新された場合に起動されます。INSERTまたはDELETEトリガーを実行する文に列リストを指定することはできません。
前述のPRINT_SALARY_CHANGESトリガーの例では、トリガーを実行する文に列リストを指定することができます。次に例を示します。
... BEFORE DELETE OR INSERT OR UPDATE OF ename ON Emp_tab ...
注意:
INSTEAD OFトリガーを指定してUPDATEに列リストを指定できません。
UPDATE OF句に指定された列がオブジェクト列の場合は、オブジェクトの属性のどれかが変更された場合にもトリガーが起動されます。
UPDATE OF句はコレクション列には指定できません。
CREATE TRIGGER文にBEFOREまたはAFTERオプションを指定して、実行中のトリガー文によってトリガー本体が起動されるタイミングを指定できます。CREATE TRIGGER文では、トリガーを実行する文の直前にBEFOREまたはAFTERオプションを指定します。たとえば、前述の例では、PRINT_SALARY_CHANGESトリガーがBEFOREトリガーです。
一般に、BEFOREトリガーまたはAFTERトリガーは、それぞれ次のような場合に使用します。
UPDATE文またはDELETE文が同時実行中のUPDATEとの競合を検出すると、Oracle DatabaseはSAVEPOINTまでの透過的ROLLBACKを実行して、更新を再起動します。文が正常に完了するまで、これは何度も行われる可能性があります。文が再起動されるたびに、BEFORE文トリガーが再起動されます。セーブポイントまでのロールバックでは、トリガー内で参照されるパッケージ変数への変更は取り消されません。パッケージには、このような状況を検出するためのカウンタ変数を含める必要があります。
リレーショナル・データベースは、SQL文による行の処理順序を保証しません。したがって、行の処理順序に基づくトリガーは作成しないでください。たとえば、グローバル変数の現在の値が、行トリガーによって処理される行に依存する場合は、行トリガー内のグローバル・パッケージ変数に値を割り当てないでください。また、グローバル・パッケージ変数の値がトリガー内で更新される場合は、これらの変数をBEFORE文トリガー内で初期化するようにしてください。
トリガー本体内の文によって他のトリガーが起動される場合、それらのトリガーはカスケードしているといいます。 Oracle Databaseでは、一度に最大32個のトリガーをカスケードできます。なお、OPEN_CURSORS初期化パラメータを使用して、カスケード可能なトリガーの数を制限することもできます。これは、トリガーを実行するたびにカーソルがオープンされるためです。
トリガーは、インラインで、またはプロシージャをコールすることによって一連の操作を実行できますが、同じ型の複数のトリガーを使用すると、同じ表に対するトリガーを持つアプリケーションのインストールをモジュール化できるため、データベース管理が強化されます。
Oracle Databaseは、別の型のトリガーを実行する前に、同じ型のすべてのトリガーを実行します。1つの表に対して同じ型のトリガーが複数ある場合、Oracle Databaseでは任意の順序を選択してこれらのトリガーを実行します。
後続の各トリガーは、前に起動されたトリガーが変更した内容を参照します。個々のトリガーは、old値およびnew値を参照できます。old値は元の値で、new値は一番最後に起動されたUPDATEトリガーまたはINSERTトリガーが設定した現在の値です。
トリガーされた複数のアクションが特定の順序で確実に実行されるようにするには、これらのアクションをまとめて1つのトリガーに統合する必要があります(たとえば、トリガーが一連のプロシージャをコールする方法を使用します)。
更新可能なビューとは、基礎となる表にDMLを実行できるビューです。元々更新可能なビューもありますが、「INSTEAD OFトリガーが必要なビュー」の項に示されている構造体の1つ以上から作成されているビューは更新できません。
前述の構造体が含まれているビューは、INSTEAD OFトリガーを使用して更新可能にできます。INSTEAD OFトリガーを使用すると、UPDATE文、INSERT文およびDELETE文では直接変更できないビューを透過的に変更できます。このようなトリガーがINSTEAD OFトリガーと呼ばれる理由は、他の種類のトリガーと異なり、Oracle Databaseはトリガーを実行する文を実行するかわりにこのトリガーを起動するためです。このトリガーは、対象となる操作を判断し、UPDATE、INSERTまたはDELETE操作を基礎となる表に対して直接実行する必要があります。
INSTEAD OFトリガーを使用して、標準的なUPDATE文、INSERT文およびDELETE文をビューに対して書き込むと、正しいアクションが実行されるようにINSTEAD OFトリガーがバックグラウンドで動作します。
INSTEAD OFトリガーをアクティブにできるのは、それぞれの行に対してのみです。
ビューの問合せに次のいずれかの構造体が含まれている場合、UPDATE文、INSERT文またはDELETE文を使用してビューを変更できません。
DISTINCT演算子
GROUP BY句、ORDER BY句、MODEL句、CONNECT BY句またはSTART WITH句
SELECTリスト内のコレクション式
SELECTリスト内の副問合せ
WITH READ ONLYに指定された副問合せ
疑似列または式が含まれたビューを更新するには、疑似列または式のどちらも参照しないUPDATE文のみを使用します。
次の例では、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_tab e, Dept_tab 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_tab WHERE empno = :n.empno; IF rowcnt = 0 THEN INSERT INTO Emp_tab (empno,ename) VALUES (:n.empno, :n.ename); ELSE UPDATE Emp_tab SET Emp_tab.ename = :n.ename WHERE Emp_tab.empno = :n.empno; END IF; SELECT COUNT(*) INTO rowcnt FROM Dept_tab WHERE deptno = :n.deptno; IF rowcnt = 0 THEN INSERT INTO Dept_tab (deptno, dept_type) VALUES(:n.deptno, :n.dept_type); ELSE UPDATE Dept_tab SET Dept_tab.dept_type = :n.dept_type WHERE Dept_tab.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用のアクションを指定できます。
INSTEAD OFトリガーによって、クライアント側でOCIコールを介してオブジェクト・ビューのインスタンスを変更できます。
クライアント側のオブジェクト・キャッシュ内でオブジェクト・ビューによって具体化されたオブジェクトを変更し、永続記憶域にそれをフラッシュするには、オブジェクト・ビューが変更可能なものでないかぎり、INSTEAD OFトリガーを指定する必要があります。ただし、オブジェクトが読込み専用の場合は、トリガーを定義して確保する必要はありません。
INSTEAD OFトリガーは、ネストした表のビューの列に対しても作成できます。このトリガーは、ネストした表の要素を更新する手段を提供します。このトリガーは、ネストした表の各更新対象要素に対して起動されます。トリガー内の行相関変数が、ネストした表の要素に対応します。この種のトリガーは、変更対象のネストした表を含む親である行にアクセスするための追加相関名も提供します。
たとえば、社員用のネストした表を含む部門ビューについて考えてみます。
CREATE OR REPLACE VIEW Dept_view AS SELECT d.Deptno, d.Dept_type, d.Dept_name, CAST (MULTISET ( SELECT e.Empno, e.Empname, e.Salary) FROM Emp_tab e WHERE e.Deptno = d.Deptno) AS Amp_list_ Emplist FROM Dept_tab d;
CAST(MULTISET..)演算子によって、部門ごとに社員の多重集合が作成されます。社員のネストした表である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 -- The insert on the nested table is translated to an insert on the base table: INSERT INTO Emp_tab VALUES ( :Employee.Empno, :Employee.Empname,:Employee.Salary, :Department.Deptno); END;
ネストした表にINSERTが実行されるとトリガーが起動され、Emp_tab表が正しい値で埋められます。次に例を示します。
INSERT INTO TABLE (SELECT d.Emplist FROM Dept_view d WHERE Deptno = 10) VALUES (1001, 'John Glenn', 10000);
この例の:department.deptno相関変数には、値10が入ります。
FOR EACH ROWオプションによって、トリガーが行トリガーになるか文トリガーになるかが決定されます。FOR EACH ROWを指定すると、トリガーを実行する文によって影響を受ける表の各行に対してトリガーが1回起動されます。FOR EACH ROWオプションを指定しないと、トリガーは該当する個々の文に対して1回のみ起動され、その文によって影響される各行に対して別々に起動されることはありません。
たとえば、次のようなトリガーを定義します。
CREATE OR REPLACE TRIGGER Log_salary_increase AFTER UPDATE ON Emp_tab 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_tab SET Sal = Sal + 1000.0 WHERE Deptno = 20;
部門20に5人の社員がいる場合、この文が入力されるとトリガーが5回起動されます。これは、5つの行が影響を受けるためです。
次のトリガーは、Emp_tab表の各UPDATEに対して1回のみ起動します。
CREATE OR REPLACE TRIGGER Log_emp_update AFTER UPDATE ON Emp_tab BEGIN INSERT INTO Emp_log (Log_date, Action) VALUES (SYSDATE, 'Emp_tab COMMISSIONS CHANGED'); END;
文レベル・トリガーは、文全体の妥当性チェックを実行するときに有効です。
行トリガー定義にトリガー制約をオプションで指定できます。これには、WHEN句にSQLのブール式を指定します。
このオプションを指定すると、WHEN句の式がトリガーの処理対象となる行ごとに評価されます。
行に対して式がTRUEと評価されると、その行のかわりにトリガー本体が起動されます。ただし、行に対して式がFALSEまたはNOT TRUEと評価された場合(NULLの場合のように不明な場合)、その行に対してトリガー本体は起動されません。WHEN句の評価は、トリガーを実行するSQL文の実行には影響しません(WHEN句の式がFALSEと評価されても、トリガーを実行する文はロールバックされません)。
たとえば、PRINT_SALARY_CHANGESトリガーでは、Empnoの新しい値が0(ゼロ)、NULLまたは負の場合、トリガー本体は実行されません。より具体的な例としては、ある列の値が他の列の値より小さいかどうかテストする場合があります。
行トリガーのWHEN句の式に相関名を指定できます。相関名については後述します。WHEN句の式はSQL式にする必要があり、副問合せを含むことはできません。WHEN句では、PL/SQL式(ユーザー定義ファンクションを含む)は使用できません。
トリガー本体は、SQL文またはPL/SQL文を含めることができるCALLプロシージャまたはPL/SQLブロックです。CALLプロシージャは、PL/SQLまたはPL/SQLラッパーにカプセル化されたJavaプロシージャのどちらかです。これらの文は、トリガーを実行する文が入力され、トリガー制約(含まれている場合)がTRUEと評価された場合に実行されます。
行トリガーのトリガー本体には、相関名、REFERENCEINGオプション、条件述語のINSERTING、DELETING、UPDATINGなどの特殊な要素を含められます。これらの要素は、PL/SQLブロックのコードにも含めることができます。
CREATE OR REPLACE PROCEDURE foo (c VARCHAR2) AS BEGIN INSERT INTO Audit_table (user_at) VALUES(c); END; CREATE OR REPLACE TRIGGER logontrig AFTER LOGON ON DATABASE -- Just call an existing procedure. The ORA_LOGIN_USER is a function -- that returns information about the event that fired the trigger. CALL foo (ora_login_user) /
トリガーは、PL/SQLで宣言されますが、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文は、トリガーを実行する文によって影響を受ける現在の行に含まれるold列値およびnew列値にアクセスできます。変更される表の各列に2つの相関名(old列値用およびnew列値用に1つずつ)があります。トリガーを実行する文の種類によっては、相関名が意味を持たない場合もあります。
INSERT文によって起動されるトリガーは、new列値に対してのみ意味のあるアクセスを行います。行はINSERTによって作成されるため、old値はNULLです。
UPDATE文によって起動されるトリガーは、BEFOREおよびAFTERの両方の行トリガーで、old列値およびnew列値の両方にアクセスします。
DELETE文によって起動されるトリガーは、:old列値に対してのみ意味のあるアクセスを行います。行を削除すると行はなくなるため、:new値はNULLです。ただし、:new値を変更しようとすると、ORA-4084が発生するため、:new値は変更できません。
元の列値は、列名の前にold修飾子を指定して参照し、新しい列値は列名の前にnew修飾子を指定して参照します。たとえば、トリガーを実行する文がEmp_tab表(列SAL、COMMなどを持つ)に対応付けられている場合、トリガー本体に文を含めることができます。次に例を示します。
IF :new.Sal > 10000 ... IF :new.Sal < :old.Sal ...
old値およびnew値は、BEFOREおよびAFTER行トリガー内で使用できます。new列値はBEFORE行トリガー内に割り当てることができますが、(AFTER行トリガーが起動される前にトリガーを実行する文が有効となるため)AFTER行トリガーにnew列値を割り当てることはできません。BEFORE行トリガーによってnew.columnの値が変更されると、同じ文によって起動されるAFTER行トリガーは、BEFORE行トリガーによって割り当てられた変更を参照します。
WHEN句のブール式には相関名を使用することもできます。oldおよびnew修飾子をトリガー本体で使用する場合は、修飾子の前にコロン(:)を付ける必要があります。ただし、修飾子をWHEN句またはREFERENCINGオプションで使用する場合、コロンは使用できません。
通常のSQLおよびCLOB列を持つPL/SQLファンクションを使用して、LOB列を他の列と同様に処理できます。また、BLOB列を持つDBMS_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, we couldn't change the new value for a LOB. -- Now, we 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トリガーの場合は、newおよびold修飾子が、ネストした表の新しい要素および古い要素に対応します。このネストした表の要素に対応する親である行は、parent修飾子を使用してアクセスできます。親相関名は、ネストした表のトリガー内でのみ意味があり有効です。
行トリガーのトリガー本体にREFERENCINGオプションを指定して、oldまたはnewとネーミングされる相関名または表の重複を避けることができます。ただし、このようなことはほとんど発生しないため、このオプションはほとんど使用されません。
たとえば、field1(数値)およびfield2(文字)の列を含むnew表があるとします。次の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操作でトリガーを起動する場合(たとえば、ON INSERT OR DELETE OR UPDATE OF Emp_tab)は、トリガー本体で条件述語のINSERTING、DELETINGおよび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_tab ... BEGIN ... IF UPDATING ('SAL') THEN ... END IF; END;
THEN句のコードは、トリガーUPDATE文がSAL列を更新する場合にのみ実行されます。このように、対象の列が変更されていない場合は、トリガーはオーバーヘッドを最小化できます。
トリガー本体の実行中に、事前定義またはユーザー定義のエラー条件または例外が発生すると、トリガーを実行する文のみでなくトリガー本体のすべての影響が(エラーが例外ハンドラによって検出された場合を除き)ロールバックされます。したがって、トリガー本体は例外を発生させることによって、トリガーを実行する文を実行しないで済みます。ユーザー定義例外は、複雑なセキュリティ認可または整合性制約を施行するトリガーによく使用されます。
これに対する唯一の例外は、対象イベントがデータベースのSTARTUP、SHUTDOWN、またはログインしているユーザーがSYSTEMのときのLOGINの場合です。このような場合は、トリガー・アクションのみがロールバックされます。
10gリリース1(10.1)以降、オブジェクト表に対するトリガーでは、OBJECT_VALUE疑似列を使用できます。OBJECT_VALUEは、オブジェクト全体を意味します。これは1つの使用例です。また、IN仮パラメータのデータ型として、OBJECT_VALUEを指定したPL/SQLファンクションもコールできます。
次に、トリガー内でのOBJECT_VALUEの使用例を示します。また、オブジェクト表tbl内の値に対する更新を追跡するために、次の例では、履歴表であるtbl_historyが作成されます。tblの場合、1〜5までの値がnに挿入されます。mは常時0です。トリガーは、DML文によって影響を受ける各行に対して1回実行される行レベルのトリガーです。tblが更新されると、トリガーにより、tbl内のオブジェクトtのold値およびnew値がtbl_historyに書き込まれます。old値およびnew値は、:OLD.OBJECT_VALUEおよび:NEW.OBJECT_VALUEです。表tblの更新が行われます(各n値が1ずつ増加します)。トリガーが動作していることを確認するために、履歴表からの選択内容が、例の終わりに示されます。
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; /
選択結果は、列nの値がすべて1ずつ増加していることを示しています。mの値は0のままです。選択結果は次のとおりです。
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_tab FOR EACH ROW BEGIN INSERT INTO Emp_tab@Remote -- <- compilation fails here VALUES ('x'); -- when dblink is inaccessible EXCEPTION WHEN OTHERS THEN INSERT INTO Emp_log VALUES ('x'); END;
トリガーは作成されたときにコンパイルされます。したがって、トリガーをコンパイルする必要があるときにリモート・サイトを使用できないと、Oracle Databaseはリモート・データベースにアクセスする文の妥当性チェックができず、コンパイルは正常に実行されません。前述の例外文の例は、トリガーがコンパイルを完了しないため実行できません。
ストアド・プロシージャはコンパイル済の形式で格納されるため、前述の例の回避策は次のようになります。
CREATE OR REPLACE TRIGGER Example AFTER INSERT ON Emp_tab FOR EACH ROW BEGIN Insert_row_proc; END; CREATE OR REPLACE PROCEDURE Insert_row_proc AS BEGIN INSERT INTO Emp_tab@Remote VALUES ('x'); EXCEPTION WHEN OTHERS THEN INSERT INTO Emp_log VALUES ('x'); END;
この例のトリガーは正常にコンパイルし、ストアド・プロシージャをコールします。このストアド・プロシージャには、リモート・データベースにアクセスするための妥当性チェック済の文がすでにあります。したがって、リンクが使用できないためにリモートINSERT文が失敗すると、例外が捕捉されます。
トリガーのコーディングには、標準PL/SQLブロックにはない、いくつかの制約があります。次の項では、トリガーのこのような制約を説明します。
トリガーのサイズは、32KB以下に指定する必要があります。
トリガー本体には、DML SQL文を含めることができます。 また、SELECT文を含めることはできますが、SELECT... INTO...文またはカーソル定義内のSELECT文を指定する必要があります。
DDL文はトリガー本体には含めることはできません。また、トランザクション制御文もトリガーには含めることはできません。ROLLBACK、COMMITおよびSAVEPOINTは使用できません。システム・トリガーの場合は、{CREATE/ALTER/DROP} TABLE文およびALTER...COMPILEを使用できます。
トリガー内の文では、リモート・スキーマ・オブジェクトを参照できます。ただし、ローカル・トリガー内からリモート・プロシージャをコールするときは、特に注意が必要です。トリガーの実行中にタイムスタンプまたはシグネチャの不一致が見つかると、リモート・プロシージャは実行されず、トリガーが無効になります。
トリガー内のLONGおよびLONG RAWデータ型には、次の制限があります。
LONGまたはLONG RAWデータ型の列にデータを挿入できます。
LONGまたはLONG RAW列のデータが制約データ型(CHARやVARCHAR2など)に変換できる場合、トリガー内の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_tab FOR EACH ROW DECLARE n INTEGER; BEGIN SELECT COUNT(*) INTO n FROM Emp_tab; DBMS_OUTPUT.PUT_LINE(' There are now ' || n || ' employees.'); END;
次のSQL文が入力されるとします。
DELETE FROM Emp_tab WHERE Empno = 7499;
行が削除されるときに表が変更中であるため、次のエラーが戻されます。
ORA-04091:表SCOTT.Emp_tabは変更しています。トリガー/関数は見ることができません
トリガーからFOR EACH ROW行を削除すると、このトリガーは文トリガーになり、制限やトリガーの対象にはなりません。
変更表を更新する必要がある場合、一時表、PL/SQL表またはパッケージ変数を使用してこれらの制限を回避することもできます。たとえば、元の表を更新する1つのAFTER行トリガーが変更表エラーとなった場合、かわりに、一時表を更新するAFTER行トリガーおよび一時表からの値を使用して元の表を更新するAFTER文トリガーの2つのトリガーを使用できる場合があります。
宣言整合性制約は、行トリガーに関して随時テストされます。
分散データベースの異なるノードの表の間では、宣言参照整合性制約はサポートされていないため、変更表の制限は、リモート・ノードにアクセスするトリガーには適用されません。これらの制限は、ループバック・データベース・リンクで接続されている同一データベース内の表の間でも施行されません。ループバック・データベース・リンクでは、リンクを含むデータベースに戻るOracle Netパスを定義して、ローカル表がリモートで表示されます。
この項の前半で説明したように、変更エラーが存在するため、親文が変更する表をトリガーが読込みまたは変更を行うことはできません。 ただし、Oracle Databaseリリース8.1以上では、親表に対して削除を行うと、BEFORE/AFTER文トリガーが1回起動されます。これによって、(行トリガー以外の)トリガーを作成して親表および子表の読込みおよび変更を行うことができます。
これによって、ほとんどの外部キー制約アクションはそれらの明白なAFTER行トリガーを経由して実装されるため、制約は自己参照的ではなくなります。更新カスケード、更新セットNULL、更新セット・デフォルト、削除セット・デフォルト、欠落した親の挿入および子件数メンテナンスは、すべて簡単に実装できます。次に、更新カスケードの実装の例を示します。
create table p (p1 number constraint ppk primary key); create table f (f1 number constraint ffk 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の値(1)から(2)への更新を行い、fに値(2)の2つの行を残します。次に、文はpの値(2)から(3)への更新を行い、トリガーはfの2つの行の値を両方とも(2)から(3)へ更新します。最後に、文はpの値(3)から(4)への更新を行い、トリガーはfの3つの行すべてを(3)から(4)へ更新します。pとfのデータの関連は失われます。
この問題を回避するため、主キーを変更する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の作成後には起動されません。Oracle Databaseでは、コミットされていないトリガーは起動されません。
外部関数のコールアウトに関するすべての制限も適用されます。
次の文では、トリガーには、トリガーの所有者の名前は戻されますが、表を更新しているユーザーの名前は戻されません。
SELECT Username FROM USER_USERS;
ご使用のスキーマに対してトリガーを作成するには、CREATE TRIGGERシステム権限および次のいずれかが必要です。
他のユーザーのスキーマ内にトリガーを作成、または自スキーマ内のトリガーから他のスキーマ内の表を参照するには、CREATE ANY TRIGGERシステム権限が必要です。この権限があると、任意のスキーマ内にトリガーを作成し、任意のユーザーの表と対応付けることができます。さらに、トリガーを作成するユーザーには、参照するプロシージャ、ファンクションまたはパッケージに対するEXECUTE権限も必要です。
DATABASEに対してトリガーを作成するには、ADMINISTER DATABASE TRIGGER権限が必要です。この権限が後になって取り消された場合、トリガーを削除することはできますが、変更することはできません。
トリガー本体で参照されるスキーマ・オブジェクトへのオブジェクト権限は、トリガーの所有者に(ロールを介さずに)明示的に付与する必要があります。トリガー本体の文は、トリガーを実行する文を発行するユーザーの権限ドメインではなく、そのトリガーの所有者の権限ドメインから操作します。これは、ストアド・プロシージャの権限モデルと類似しています。
トリガーは、無名PL/SQLブロックに:newおよび:old機能を追加したものと類似していますが、コンパイル方法が異なります。無名PL/SQLブロックは、メモリーにロードされると、常に、コンパイルされます。コンパイルには、次の3段階が必要です。
これに対して、トリガーは、CREATE TRIGGER文が入力されたときに完全にコンパイルされ、pcodeはデータ・ディクショナリに格納されます。そのため、トリガーを起動するときに、共有カーソルをオープンしてトリガー・アクションを実行する必要はなくなります。そのかわり、トリガーは直接実行されます。
トリガーのコンパイル中にエラーが発生しても、トリガーは作成されます。ただし、DML文がこのトリガーを起動すると、そのDML文は失敗します。(ランタイム・トリガー・エラーが発生すると、DML文は必ず失敗します。)トリガーの作成時にすべてのコンパイル・エラーが表示されるように、SQL*PlusまたはEnterprise Manager内でSHOW ERRORS文を使用するか、またはUSER_ERRORSビューからエラーをSELECTすることができます。
コンパイル済のトリガーには依存関係があります。このようなトリガーは、トリガー本体からコールされるファンクションまたはストアド・プロシージャのような依存対象となるオブジェクトが修正されると無効になります。依存性の理由で無効になったトリガーは、次に起動された時点で再コンパイルされます。
ALL_DEPENDENCIESビューを調べると、トリガーの依存関係がわかります。たとえば、次の文は、SCOTTスキーマ内のトリガーの依存関係を示します。
SELECT NAME, REFERENCED_OWNER, REFERENCED_NAME, REFERENCED_TYPE FROM ALL_DEPENDENCIES WHERE OWNER = 'SCOTT' and TYPE = 'TRIGGER';
トリガーは他のファンクションまたはパッケージに依存することがあります。トリガー内に指定されているファンクションまたはパッケージが削除されると、トリガーは無効とマークされます。イベントの発生時点で、トリガーの有効性が妥当性チェックされます。トリガーが有効でない場合は、VALID WITH ERRORSとマークされ、イベントが失敗します。
トリガーを手動で再コンパイルするには、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システム権限が必要です。
ストアド・プロシージャで使用できる機能と同じ機能を使用して、トリガーをデバッグできます。
トリガーは、次の2つのモードのどちらかです。
使用可能:トリガーを実行する文が入力され、トリガー制限が(存在する場合に)TRUEと評価された場合、使用可能トリガーによってトリガー本体が実行されます。
使用禁止:トリガーを実行する文が入力され、トリガー制限が(存在する場合に)TRUEと評価された場合でも、使用禁止トリガーはトリガー本体を実行しません。
デフォルトでは、トリガーは、作成時に自動的に使用可能に設定されます。ただし、トリガーは後で使用禁止にできます。トリガーを使用禁止にする必要がある作業を終了した後は、トリガーが適切なときに起動されるように、再び使用可能にしておきます。
使用禁止にしたトリガーを使用可能にするには、ALTER TRIGGER文のENABLEオプションを使用します。INVENTORY表の使用禁止にされたREORDERトリガーを使用可能にするには、次のように入力します。
ALTER TRIGGER Reorder ENABLE;
ALTER TABLE文のENABLE句にALL TRIGGERSオプションを使用すると、1つの文で、ある表に定義されているすべてのトリガーを使用可能にできます。たとえば、INVENTORY表に定義されているすべてのトリガーを使用可能にするには、次のように指定します。
ALTER TABLE Inventory ENABLE ALL TRIGGERS;
次のような場合、一時的にトリガーを使用禁止にできます。
デフォルトでは、トリガーは作成時に使用可能に設定されます。トリガーを使用禁止にするには、ALTER TRIGGER文のDISABLEオプションを使用します。
たとえば、INVENTORY表のREORDERトリガーを使用禁止にするには、次のように指定します。
ALTER TRIGGER Reorder DISABLE;
ALTER TABLE文のDISABLE句にALL TRIGGERSオプションを使用すると、1つの文で、ある表に対応づけられたすべてのトリガーを使用禁止にできます。たとえば、INVENTORY表に定義されているすべてのトリガーを使用禁止にするには、次のように指定します。
ALTER TABLE Inventory DISABLE ALL TRIGGERS;
次のデータ・ディクショナリ・ビューには、トリガーに関する情報が表示されます。
新しい列BASE_OBJECT_TYPEは、トリガーがDATABASE、SCHEMA、表またはビューのどれに基づくかを示します。基になるオブジェクトが表またはビューでない場合は、古い列TABLE_NAMEがNULLです。
ACTION_TYPE列は、トリガーがコール型のトリガーかPL/SQLトリガーかを示します。
TRIGGER_TYPE列には、システム・イベントにのみ適用される他の2つの値、BEFORE EVENTおよびAFTER EVENTが含まれます。
TRIGGERING_EVENT列には、システム・イベントおよびDMLイベントがすべて含まれます。
たとえば、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;
トリガーを使用して、様々な方法でOracle Databaseの情報管理をカスタマイズできます。一般に、トリガーは次の用途に使用します。
この項では、これらのトリガー・アプリケーションの例を紹介します。これらの例をそのまま使用することはできませんが、トリガーを設計するときの参考にしてください。
トリガーは、Oracle Databaseの組込み監査機能を補うためによく使用されます。トリガーを作成して、AUDITコマンドによって記録される情報と同様の情報を記録することはできますが、トリガーは、より詳細な監査情報が必要な場合に使用します。たとえば、トリガーを使用すると、行単位の値に基づく監査が可能です。
AUDIT文が、機密保護監査機能と考えられていることに対して、トリガーは、財務監査機能を提供します。
データベース・アクティビティを監査するトリガーを作成するかどうかを判断するときは、表9-1に示されているように、トリガーで定義される監査に比べてOracle Databaseの監査機能で何が提供されるかを検討します。
トリガーを使用して高度な監査を行うには、通常、AFTERトリガーを使用します。AFTERトリガーを使用すると、トリガーを実行する文が適切な整合性制約に従った後で、監査情報が記録されます。これによって、整合性制約の例外を生成する文に対する無効な監査処理の実行を防止します。
AFTER行トリガーとAFTER文トリガーの使い分けは、監査情報に応じて異なります。たとえば、行トリガーを使用すると、表の行単位の値に基づく監査が可能です。トリガーでは、監査済SQL文を発行するための理由コードの入力をユーザーに要求することもできます。これは、行レベルおよび文レベルの両方の監査状況に有効です。
次の例では、Emp_tab表に対する変更を行ベースで監査するトリガーを示します。この例では、更新前に理由コードをグローバル・パッケージ変数に格納する必要があります。トリガーを使用して値ベースの監査を実行する方法、およびパブリック・パッケージ変数を使用する方法を示します。
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 could be set by the application by a command such as EXECUTE AUDITPACKAGE.SET_REASON(reason_string). Note that 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 the preceding conditional evaluates to TRUE, the user-specified error number and message is raised, the trigger stops execution, and the effects of the triggering statement are rolled back. Otherwise, a new row is inserted into the predefined auditing table named AUDIT_EMPLOYEE containing the existing and new values of the Emp_tab table and the reason code defined by the REASON variable of AUDITPACKAGE. Note that the "old" values are NULL if triggering statement is an INSERT and the "new" values are NULL if the triggering statement is a 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_tab BEGIN auditpackage.set_reason(NULL); END;
前述のトリガーは、2つとも同じ種類のSQL文によって起動されます。ただし、AFTER行トリガーが、トリガーを実行する文によって影響を受ける表の行ごとに1回起動されるのに対して、AFTER文トリガーは、トリガーを実行する文の実行が終了したときに1回のみ起動されます。
次に示すトリガーもトリガーを使用して監査を行います。このトリガーは、Emp_tab表に加えられた変更を追跡し、この情報をAUDIT_TABLEとAUDIT_TABLE_VALUESに格納します。
CREATE OR REPLACE TRIGGER Audit_emp AFTER INSERT OR UPDATE OR DELETE ON Emp_tab FOR EACH ROW DECLARE Time_now DATE; Terminal CHAR(10); BEGIN -- get current time, and the terminal of the 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_tab', 'INSERT', :new.Empno); -- record primary key of the deleted row: ELSIF DELETING THEN INSERT INTO Audit_table VALUES (Audit_seq.NEXTVAL, User, Time_now, Terminal, 'Emp_tab', 'DELETE', :old.Empno); -- for updates, record the primary key -- of the row being updated: ELSE INSERT INTO Audit_table VALUES (audit_seq.NEXTVAL, User, Time_now, Terminal, 'Emp_tab', 'UPDATE', :old.Empno); -- and for SAL and DEPTNO, record old and 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の宣言整合性制約機能がサポートするルールと同じルールの多くを施行できますが、トリガーは、標準の整合性制約では定義できない複雑なビジネス・ルールを規定するためにのみ使用するようにしてください。Oracle Databaseの宣言整合性制約機能には、トリガーで定義する制約に比べて、次のメリットがあります。
一元化された整合性チェック:すべてのデータ・アクセス・ポイントは、各スキーマ・オブジェクトに対応する整合性制約によって定義されたグローバルな一連のルールに準拠する必要があります。
宣言方式:標準の整合性制約機能を使用して定義された制約は、トリガーで定義された同等の制約と比較して、より作成しやすくエラーが発生しにくいというメリットがあります。
データ整合性のほとんどは、宣言整合性制約によって定義して施行できますが、トリガーは宣言整合性制約では定義できない複雑なビジネス制約の施行に使用できます。たとえば、トリガーを使用して次を施行できます。
UPDATE SET NULL、およびUPDATEとDELETE SET DEFAULTの参照アクション
CHECK制約で指定できる式では定義できない複雑なCHECK制約
トリガーを使用して参照整合性を施行できる場合が多々あります。ただし、トリガーを使用するのは、実行しているアクションに宣言整合性がサポートされていない場合のみにしてください。
トリガーを使用して参照整合性をメンテナンスするときは、親表に主キー(または一意キー)制約を宣言します。同じデータベース内の親表と子表間の参照整合性をトリガーでメンテナンスしている場合は、子表にも外部キーを宣言できますが、外部キーは使用禁止に設定してください。使用禁止にすることによって、対応する主キー制約が(CASCADEオプションを使用して、主キー制約を明示的に削除しないかぎり)削除されなくなります。
トリガーを使用して参照整合性をメンテナンスするには、次のようにします。
RESTRICT、CASCADEまたはSET NULL)が実行されることを保証します。親表への挿入にアクションは不要です(依存する外部キーはありません)。
次の項では、参照整合性の規定に必要なトリガーの例を紹介します。これらの例ではEmp_tab表およびDept_tab表を使用します。
トリガーのいくつかには、行をロックする文(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_tab FOR EACH ROW WHEN (new.Deptno IS NOT NULL) -- Before a row is inserted, or DEPTNO is updated in the Emp_tab -- table, fire this trigger to verify that the new foreign -- key value (DEPTNO) is present in the Dept_tab table. DECLARE Dummy INTEGER; -- to be used 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 can't be -- deleted by another transaction until this -- transaction is committed or rolled back. CURSOR Dummy_cursor (Dn NUMBER) IS SELECT Deptno FROM Dept_tab 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 and 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;
次のトリガーをDEPT_TAB表に定義し、DEPT_TAB表の主キーに対してUPDATEおよびDELETE RESTRICT参照アクションを施行します。
CREATE OR REPLACE TRIGGER Dept_restrict BEFORE DELETE OR UPDATE OF Deptno ON Dept_tab FOR EACH ROW -- Before a row is deleted from Dept_tab or the primary key -- (DEPTNO) of Dept_tab is updated, check for dependent -- foreign key values in Emp_tab; rollback if any are found. DECLARE Dummy INTEGER; -- to be used 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_tab 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 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;
次のトリガーをDEPT_TAB表に定義し、DEPT_TAB表の主キーに対してUPDATEおよびDELETE SET NULL参照アクションを施行します。
CREATE OR REPLACE TRIGGER Dept_set_null AFTER DELETE OR UPDATE OF Deptno ON Dept_tab FOR EACH ROW -- Before a row is deleted from Dept_tab or the primary key -- (DEPTNO) of Dept_tab is updated, set all corresponding -- dependent foreign key values in Emp_tab to NULL: BEGIN IF UPDATING AND :OLD.Deptno != :NEW.Deptno OR DELETING THEN UPDATE Emp_tab SET Emp_tab.Deptno = NULL WHERE Emp_tab.Deptno = :old.Deptno; END IF; END;
DEPT_TAB表に対する次のトリガーは、DEPT_TAB表の主キーに対してDELETE CASCADE参照アクションを施行します。
CREATE OR REPLACE TRIGGER Dept_del_cascade AFTER DELETE ON Dept_tab FOR EACH ROW -- Before a row is deleted from Dept_tab, delete all -- rows from the Emp_tab table whose DEPTNO is the same as -- the DEPTNO being deleted from the Dept_tab table: BEGIN DELETE FROM Emp_tab WHERE Emp_tab.Deptno = :old.Deptno; END;
次のトリガーは、Dept_tab表の部門番号が更新されたときに、その変更がEmp_tab表の依存外部キーに確実に伝播されるようにします。
-- Generate a sequence number to be used as a flag for -- determining if an update has occurred on a 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_tab ADD Update_id NUMBER; CREATE OR REPLACE TRIGGER Dept_cascade1 BEFORE UPDATE OF Deptno ON Dept_tab DECLARE Dummy NUMBER; -- Before updating the Dept_tab table (this is a statement -- trigger), generate a new sequence number and assign -- it to the public variable UPDATESEQ of a user-defined -- package named INTEGRITYPACKAGE: BEGIN SELECT Update_sequence.NEXTVAL INTO Dummy FROM dual; Integritypackage.Updateseq := Dummy; END; CREATE OR REPLACE TRIGGER Dept_cascade2 AFTER DELETE OR UPDATE OF Deptno ON Dept_tab FOR EACH ROW -- For each department number in Dept_tab that is updated, -- cascade the update to dependent foreign keys in the -- Emp_tab table. Only cascade the update if the child row -- has not already been updated by this trigger: BEGIN IF UPDATING THEN UPDATE Emp_tab SET Deptno = :new.Deptno, Update_id = Integritypackage.Updateseq --from 1st WHERE Emp_tab.Deptno = :old.Deptno AND Update_id IS NULL; /* only NULL if not updated by the 3rd trigger fired by this same triggering statement */ END IF; IF DELETING THEN -- Before a row is deleted from Dept_tab, delete all -- rows from the Emp_tab table whose DEPTNO is the same as -- the DEPTNO being deleted from the Dept_tab table: DELETE FROM Emp_tab WHERE Emp_tab.Deptno = :old.Deptno; END IF; END; CREATE OR REPLACE TRIGGER Dept_cascade3 AFTER UPDATE OF Deptno ON Dept_tab BEGIN UPDATE Emp_tab SET Update_id = NULL WHERE Update_id = Integritypackage.Updateseq; END;
トリガーは、参照整合性以外の整合性ルールも施行できます。たとえば、次のトリガーは、トリガーを実行する文の実行を許可する前に、複雑なチェックを実行します。
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 the minimum and maximum salary for the employee's new job classification from the SALGRADE table into MINSAL and MAXSAL: */ SELECT Minsal, Maxsal INTO Minsal, Maxsal FROM Salgrade WHERE Job_classification = :new.Job; /* If the employee's new salary is less than or greater than the job classification's limits, the exception is raised. The exception message is returned and the 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;
トリガーは、一般に、表データに対する複雑なセキュリティ認可の施行によく使用されます。トリガーは、Oracle Databaseが提供するデータベース・セキュリティ機能では定義できない複雑なセキュリティ認可の施行にのみ使用します。たとえば、トリガーを使用して、週末、休日および休業日には、Emp_tab表の給与データを更新できないようにすることができます。
複雑なセキュリティ認可の施行にトリガーを使用する場合は、BEFORE文トリガーを使用すると最も効果的です。BEFORE文トリガーを使用すると、次のようなメリットがあります。
次の例は、セキュリティを施行するために使用するトリガーを示します。
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); /* TRUNC gets rid of 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,'May not change ' ||'employee table during the weekend'); WHEN Not_on_holidays THEN Raise_application_error(-20325,'May not change ' ||'employee table during a holiday'); WHEN Non_working_hours THEN Raise_application_error(-20326,'May not change ' ||'Emp_tab table during non-working hours'); END;
特定のイベントに続いてデータベースに透過的な変更を実行する場合、トリガーは非常に効果的です。
REORDERトリガーの例では、一定の条件が満たされると、必要に応じて部品を再注文するトリガーを示します。(トリガーを実行する文が入力され、PARTS_ON_HAND値がREORDER_POINT値より小さい場合です。)
トリガーは、INSERT文またはUPDATE文に指定される値に基づいて、列の値を自動的に取得できます。この種のトリガーは、同一行上の別の列の値に依存する特定の列に値を埋め込む場合に便利です。この種の操作を実行するには、BEFORE行トリガーが必要ですが、これは次の理由によります。
INSERTまたはUPDATEが発生する前に依存値を取得する必要があります。
INSERT文またはUPDATE文によって影響される行ごとに、トリガーを起動する必要があります。
次の例では、行が挿入または更新されるたびに、表の新しい列値を導出するトリガーの使用方法を示します。
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. Users should be restricted 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 | 参照先 | Title | Author | Available |
|---|---|---|---|---|
|
121001 |
Classic |
Iliad |
Homer |
Y |
|
121002 |
Novel |
Gone With the Wind |
Mitchell M |
N |
このライブラリは、library_table(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に対してトリガーを定義して、ネストした表の要素の変更を処理することもできます。
システム・トリガーは、アプリケーション・コンテキストの設定に使用できます。アプリケーション・コンテキストは比較的新しい機能であり、ファイングレイン・アクセス・コントロールを実装する機能が向上します。アプリケーション・コンテキストは保護されたセッション・キャッシュで、セッション固有の属性の格納に使用できます。
次に示す例では、set_ctxプロシージャがユーザー・プロファイルに基づいてアプリケーション・コンテキストを設定します。setexpensectxトリガーは、コンテキストがユーザーごとに確実に設定されるようにします。
CONNECT secdemo/secdemo CREATE OR REPLACE CONTEXT Expenses_reporting USING Secdemo.Exprep_ctx; REM ================================================================= REM Creation of the package which implements the context: REM ================================================================= CREATE OR REPLACE PACKAGE Exprep_ctx AS PROCEDURE Set_ctx; END; SHOW ERRORS CREATE OR REPLACE PACKAGE BODY Exprep_ctx IS PROCEDURE Set_ctx IS Empnum NUMBER; Countrec NUMBER; Cc NUMBER; Role VARCHAR2(20); BEGIN -- SET emp_number: SELECT Employee_id INTO Empnum FROM Employee WHERE Last_name = SYS_CONTEXT('userenv', 'session_user'); DBMS_SESSION.SET_CONTEXT('expenses_reporting','emp_number', Empnum); -- SET ROLE: SELECT COUNT (*) INTO Countrec FROM Cost_center WHERE Manager_id=Empnum; IF (countrec > 0) THEN DBMS_SESSION.SET_CONTEXT('expenses_reporting','exp_role','MANAGER'); ELSE DBMS_SESSION.SET_CONTEXT('expenses_reporting','exp_role','EMPLOYEE'); END IF; -- SET cc_number: SELECT Cost_center_id INTO Cc FROM Employee WHERE Last_name = SYS_CONTEXT('userenv','session_user'); DBMS_SESSION.SET_CONTEXT(expenses_reporting','cc_number',Cc); END; END;
CREATE OR REPLACE TRIGGER Secdemo.Setexpseetx AFTER LOGON ON DATABASE CALL Secdemo.Exprep_etx.Set_otx
システム・イベントを発行することによって、アプリケーションは、他のアプリケーションからのメッセージにサブスクライブするのと同じ方法で、データベース・イベントにサブスクライブできます。システム・イベント発行フレームワークには、次の機能が含まれています。
トリガーを作成すると、イベント発生時に実行するプロシージャを指定できます。DMLイベントは表でサポートされ、システム・イベントは、データベースおよびスキーマでサポートされます。ALTER TRIGGER文を使用してトリガーを使用可能および使用禁止にすると、通知をオンおよびオフにすることができます。
この機能は、アドバンスト・キューイング・エンジンに統合されています。パブリッシュ/サブスクライブ・アプリケーションは、DBMS_AQ.ENQUEUE()プロシージャを使用し、他のアプリケーション(カートリッジなど)は、コールアウトを使用します。
データベースによってイベントが検出されると、トリガー・メカニズムがトリガー内に指定されているアクションを実行します。このアクションの一部として、DBMS_AQパッケージを使用してイベントをキューに発行でき、キューによって、サブスクライバが通知を取得します。
イベントが発生すると、そのイベントに対して使用可能なすべてのトリガーがデータベース上で起動されます。これには次の例外があります。
DROPイベントのトリガーは、削除される場合は起動されません。
オブジェクトに対して複数のトリガーを作成できます。イベントが複数のトリガーを起動する場合、起動順序は定義されないため、特定の順序内で起動されるトリガーに依存しないでください。
イベントが発行されるときは、パラメータ・リストに指定されている特定の実行時コンテキストおよび属性がコールアウト・プロシージャに渡されます。イベント属性関数と呼ばれる一連の関数が提供されています。
サポートされているシステム・イベントごとに、イベント固有の属性が指定され、事前定義されています。パラメータ・リストには、他の単純な式とともにこの属性のいずれかを選択できます。コールアウトの場合、これらはIN引数として渡されます。
発行コールアウト関数からの戻り状態は、すべてのイベントに関して無視されます。たとえば、SHUTDOWNイベントの場合、データベースは戻り状態に関しては何も実行できません。
トリガーは、従来からトリガーの定義者として実行されてきました。イベントのトリガー・アクションは、アクションの定義者として(コールアウト内のパッケージまたはファンクションの定義者、またはキュー内のトリガーの所有者として)実行されます。トリガーの所有者には、基になるキュー、パッケージまたはプロシージャに対するEXECUTE権限が必要なため、この動作には一貫性があります。
データベースでトリガーが起動されると、トリガーを起動したイベントの属性を検索できます。ファンクション・コールを使用して各属性を検索できます。表9-2では、システムで定義されたイベント属性について説明します。
この項では、重要なシステム・イベントおよびクライアント・イベントについて説明します。
システム・イベントは、個々の表または行ではなく、インスタンスまたはスキーマ全体に関係します。起動イベントおよび停止イベントに対して作成されたトリガーは、データベース・インスタンスに関連付けられる必要があります。エラー・イベントおよび一時停止イベントに対して作成されたトリガーは、データベース・インスタンスまたは特定のスキーマのいずれかへの関連付けが可能です。
表9-3に、システム・マネージャ・イベントのリストを示します。
クライアント・イベントは、ユーザーのログイン/ログオフ、DMLおよびDDLの操作に関係するイベントです。次に例を示します。
CREATE OR REPLACE TRIGGER On_Logon AFTER LOGON ON The_user.Schema BEGIN Do_Something; END;
LOGONイベントおよびLOGOFFイベントによって、UIDおよびUSERに単純な条件を使用できます。 他のすべてのイベントによって、UIDやUSERのような関数のみでなく、オブジェクトの型および名前に単純な条件を使用できます。
LOGONイベントは、トリガーの起動後、別のトランザクションを開始してこれをコミットします。他のすべてのイベントは、既存のユーザー・トランザクションでトリガーを起動します。
LOGONイベントおよびLOGOFFイベントは、すべてのオブジェクトを操作できます。他のすべてのイベントでは、対応するトリガーは、そのイベントを生成させるオブジェクト上でDROPやALTERなどのDDL操作を実行できません。
これらのトリガーで使用できるDDLは、表の変更、作成または削除、トリガーの作成および処理のコンパイルです。
イベント・トリガーがDDL操作(CREATE TRIGGERなど)の対象になる場合、このトリガーを、後で同じトランザクション中に起動することはできません。
表9-4に、クライアント・イベントのリストを示します。
|
![]() Copyright © 2006 Oracle Corporation. All Rights Reserved. |
|