天天看點

PL/SQL12.1 ——DML 觸發器

 何謂觸發器?簡言之,是一段命名的PL/SQL代碼塊,隻不過該代碼塊在特定的條件下被觸發并且執行。對于這樣的代碼我們稱之為觸發器

。觸發器根據觸發類型的不同又分為不同級别的觸發器,下面将給出觸發器的分類,定義,以及使用的示例。

一、觸發器的相關概念

    1.觸發器的分類

        通常根據觸發條件以及觸發級别的不同分為DML觸發器,INSTEAD OF 觸發器,系統事件觸發器。

        DML觸發器

            ORACLE 對DML語句進行觸發,可以在DML操作前或操作後進行觸發,并且可以對每個行或語句操作上進行觸發。

        INSTEAD OF 觸發器

            在ORACLE裡,對于簡單視圖,可以直接使用DML進行操作,而複雜視圖則不能直接使用DML,是以INSTEAD OF 觸發器應運而生。

            INSTEAD OF 觸發器主要是為解決複雜視圖不能執行DML而建立。

        系統事件觸發器 

            在 ORACLE 資料庫系統的事件中進行觸發,如ORACLE系統的啟動與關閉等.使用系統觸發器,便于系統跟蹤,監測資料庫變化情況等。

    2.觸發器的組成(一段PL/SQL代碼塊,可以由PL/SQL,Java,C進行開發,特定事件發生将被觸發)

        a.觸發事件

            Oracle 啟動、關閉

            Oracle 錯誤消息

            使用者登入與斷開會話

            特定的表、視圖上的DML操作

            基于schema的DDL操作

        b.觸發時間

            即該TRIGGER 是在觸發事件發生之前(BEFORE)還是之後(AFTER)觸發,也就是觸發事件和該TRIGGER 的操作順序。 

        c.觸發器本身

            指實際的觸發代碼,當觸發事件發生後,觸發器代碼決定将做何種操作。

            觸發器代碼大小不能超過32k,對于超長的代碼可以将其置于單獨的存儲過程,然後在觸發器中使用call 調用該過程。

            觸發器代碼隻能包含DQL,DML,而不能包含DDL以及事務控制語言(COMMIT,ROLLBACK,SAVEPOINT) 。

        d.觸發頻率

            說明觸發器内定義的動作被執行的次數。即語句級(STATEMENT)觸發器和行級(ROW)觸發器。

            語句級(STATEMENT)觸發器:是指當某觸發事件發生時,該觸發器隻執行一次。

            行級(ROW)觸發器:是指當某觸發事件發生時,對受到該操作影響的每一行資料,觸發器都單獨執行一次。

    3.觸發器的用途

        控制DDL語句的行為,如通過更改、建立或重命名對象

        控制DML語句的行為,如插入、更新和删除

        實施參照完整性、複雜業務規則和安全性政策

        在修改視圖中的資料時控制和重定向DML語句

        通過建立透明日志來稽核系統通路和行為的資訊

二、建立DML觸發器文法描述

    1.建立觸發器的文法

        CREATE [OR REPLACE] TRIGGER trigger_name

        {BEFORE | AFTER | INSTEAD OF}    

        {INSERT | DELETE | UPDATE [OF

column [, column …]]}                  --定義觸發類型,即那一種或多種DML以及特定的列

        ON {[schema.] table_name | [schema.] view_name}                       --特定的觸發對象,表或視圖

        [REFERENCING {OLD [AS] old | NEW [AS] new| PARENT as parent}]

        [FOR EACH ROW ]                                                       --定義觸發器為行級觸發器

        [WHEN condition]

        BEGIN

            trigger_body;

        END;

        BEFORE | AFTER | INSTEAD OF

            BEFORE指在執行DML之前觸發觸發器,AFTER則是指在DML執行之後觸發觸發器

            INSTEAD OF觸發器隻針對視圖和對象視圖建立,而不能對表、模式和資料庫建立INSTEAD OF 觸發器

        [REFERENCING {OLD [AS] old | NEW [AS] new| PARENT as parent}]

            說明相關名稱,在行觸發器的PL/SQL塊和WHEN 子句中可以使用相關名稱參照目前的新,舊列值,預設的相關名稱分别為OLD和NEW。

            觸發器的PL/SQL塊中應用相關名稱時,必須在它們之前加冒号(:),但在WHEN子句中則不能加冒号。

        [FOR EACH ROW ]

            定義觸發器為行級觸發器。

            行級觸發器和語句級觸發器的差別表現在:一個DML語句可能操縱多行,也可能操縱一行,使用行級觸發器,不論是一行還是多

            行資料被操縱,行觸發器為該DML的每一行觸發一次觸發器操作。語句級觸發器将整個語句操作作為觸發事件,不論該語句影響

            了多少行,僅僅觸發一次觸發器。

            當省略FOR EACH ROW 選項時,BEFORE 和AFTER 觸發器為語句觸發器,而INSTEAD OF 觸發器則為行觸發器。   

            觸發條件,當條件為TRUE時,觸發器代碼才會被執行,對于DML觸發器,僅僅允許在行級觸發器上指定觸發條件。

            condition 為一個邏輯表達時,其中必須包含相關名稱,而不能包含查詢語句,也不能調用PL/SQL 函數。

WHEN 子句可通過引用new或old僞記錄、一個元件選擇符和一個列名來通路僞字段。

            WHEN 子句不能用在INSTEAD OF 行觸發器和其它類型的觸發器中。

    2.DML觸發器的觸發順序

        a.在單行資料上的觸發順序(觸發代碼僅被執行一次)

            BEFORE 語句級觸發器

                BEFORE 行級觸發器

                AFTER 行級觸發器

            AFTER 語句級觸發器

        b.在多行資料上的觸發順序(語句級觸發器僅被執行一次,行級觸發器在每個作業行上被執行一次)

    3.觸發器中的條件謂詞

        ORACLE 提供三個參數INSERTING, UPDATING, DELETING 用于判斷觸發了哪些操作。

        INSERTING:如果觸發語句是INSERT 語句,則為TRUE,否則為FALSE 。

        UPDATING:如果觸發語句是UPDATE語句,則為TRUE,否則為FALSE 。

        DELETING:如果觸發語句是DELETE 語句,則為TRUE,否則為FALSE 。

    4.NEW、OLD 限定符的使用

        使用被插入、更新或删除的記錄中的列值,可以使用NEW和OLD限定符來表示

        :old 修飾符通路操作完成前列的值

        :new 修飾符通路操作完成後列的值

        限定符    INSERT操作     UPDATE操作  DELETE操作

        ---------  --------------  -----------  ----------

        OLD        NULL            有效         有效

        NEW        有效           有效         NULL

三、建立DML觸發器  

    1.建立BEFORE 語句級觸發器

        sys@ORCL> drop user scott cascade;     --删除scott方案

        sys@ORCL> start $ORACLE_HOME/rdbms/admin/utlsampl.sql   --重建scott方案

        sys@ORCL> grant dba to scott;  --授予Scott

DBA角色

        scott@ORCL> create table emp_check(oper varchar2(30),upd_date

date);    --建立表存放emp表的更新記錄操作的跟蹤   

        CREATE OR REPLACE TRIGGER tr_before_update_emp    --建立update觸發器

          BEFORE UPDATE ON emp  

          -- FOR EACH ROW

          INSERT INTO emp_check

          Values

            ('Before update, statement level', sysdate);

        scott@ORCL> select * from emp_check;  --未執行update前,跟蹤表記錄為空

        no rows selected

        scott@ORCL> update emp set sal = sal + 100 where deptno = 20;   --更新了四條記錄

        4 rows updated.

        scott@ORCL> select * from emp_check;     --跟蹤表表插入了一條跟蹤記錄

        OPER                           UPD_DATE

        ------------------------------ ---------

        Before update, statement level 24-DEC-10

        scott@ORCL> update emp set sal = sal + 200 where empno = 7369; --更新了一條記錄,跟蹤表再次插入一條新記錄

        1 row updated.

        scott@ORCL> select * from emp_check;

        Before update, statement level 24-DEC-10       

    2.建立 BEFORE 行級觸發器   

        使用上面的代碼來建立行級觸發器,與之不同的是将上面的代碼中"-- FOR EACH ROW" 的"--"删除,則建立的觸發器即為行級觸發器

        代碼省略

            scott@ORCL> update emp set sal = sal + 200 where deptno = 20; --再次更新deptno為的記錄,且記錄總數為四條

            4 rows updated.                    

            scott@ORCL> select * from emp_check;   --表emp_check中增加四條,即為update的每一行增加一條記錄

            OPER                           UPD_DATE

            ------------------------------ ---------

            Before update, statement level 24-DEC-10

            Before update, statement level 24-DEC-10   

    3.建立 AFTER 語句級觸發器      

        CREATE TABLE audit_table_emp   --建立一張表audit_table_emp存放emp表上DML操作的次數

        (

            name        VARCHAR2(20),

            ins     INT,

            upd     INT,

            del     INT,

            starttime   DATE,

            endtime     DATE

        );

        CREATE OR REPLACE TRIGGER tr_audit_emp

        AFTER INSERT OR UPDATE OR DELETE ON emp

        DECLARE

            v_temp INT;

            SELECT COUNT(*) INTO v_temp FROM audit_table_emp WHERE name = 'EMP';

            IF v_temp = 0 THEN

                INSERT INTO audit_table_emp VALUES('EMP', 0, 0, 0, SYSDATE, NULL);

            END IF;

            CASE

                WHEN INSERTING THEN  --注意此例中條件謂詞的使用INSERTING、UPDATING、DELETING

                    UPDATE audit_table_emp SET ins = ins + 1, endtime = SYSDATE WHERE name = 'EMP';

                WHEN UPDATING THEN

                    UPDATE audit_table_emp SET upd = upd + 1, endtime = SYSDATE WHERE name = 'EMP';

                WHEN DELETING THEN

                    UPDATE audit_table_emp SET del = del + 1, endtime = SYSDATE WHERE name = 'EMP';

            END CASE;

        END;   

        scott@ORCL> update emp set sal=sal+200 where empno=7788; 

        scott@ORCL> update emp set sal=sal+200 where ename='SMITH';

        scott@ORCL> delete from emp where empno=7788; 

        scott@ORCL> select * from audit_table_emp;   --兩次更新及一次被記錄到表中

        NAME                        INS        UPD        DEL STARTTIME ENDTIME

        -------------------- ---------- ---------- ---------- --------- ---------

        EMP                           0          2          1 24-DEC-10 24-DEC-10  

        scott@ORCL> update emp set sal=sal+100 where deptno=10;

        3 rows updated.   --更新了行,當audit_table_emp表中僅僅記錄一次,UPD的值增加到

        scott@ORCL> select * from audit_table_emp;

        EMP                           0          3          1 24-DEC-10 24-DEC-10      

    4.建立 AFTER 行級觸發器

        CREATE TABLE audit_emp_change    --建立audit_emp_change存放emp 表sal列被更新前後的值

            name        VARCHAR2(10),

            oldsal      NUMBER(6, 2),

            newsal      NUMBER(6, 2),

            time        DATE

        CREATE OR REPLACE TRIGGER tr_sal_change 

        AFTER UPDATE OF sal ON emp  --注意update 觸發器中使用了OF 關鍵字,當sal列發生變化時,tr_sal_change被觸發

        FOR EACH ROW                --使用行級觸發器

        --WHEN (old.job='CLERK')

            SELECT COUNT(*) INTO v_temp FROM audit_emp_change WHERE name = :old.ename;  --注意OLD 與NEW 的使用

                INSERT INTO audit_emp_change VALUES(:old.ename, :old.sal, :new.sal, SYSDATE);

            ELSE

                UPDATE audit_emp_change SET oldsal = :old.sal, newsal = :new.sal, time = SYSDATE WHERE name = :old.ename;

        scott@ORCL> update emp set sal=sal-100 where empno=7369;  --更新一行

        scott@ORCL> select * from audit_emp_change;    --audit_emp_change表中記錄了一行

        NAME           OLDSAL     NEWSAL TIME

        ---------- ---------- ---------- ---------

        SMITH            1400       1300 24-DEC-10

        scott@ORCL> delete from audit_emp_change;  --删除之前的記錄

        scott@ORCL> update emp set sal=sal+200 where deptno=10;   --更新了三行

        3 rows updated.

        scott@ORCL> select * from audit_emp_change;   --audit_emp_change表中記錄了三行

        CLARK            2550       2750 24-DEC-10

        KING             5100       5300 24-DEC-10

        MILLER           1400       1600 24-DEC-10 

    5.限制行級觸發器

        限制行級觸發器是通過添加[WHEN condition]判斷條件,來對滿足特定condition的記錄觸發觸發器。

        對于DML觸發器而言,僅僅允許在行級觸發器上指定觸發條件。

        在上面第4小點建立AFTER 行級觸發器的代碼中,這一行"--WHEN (old.job='CLERK')" 去掉"--",觸發器變為具有限定條件的行級

        觸發器,則對特定的條件,即job='CLERK'的記錄在更新其sal時才會被觸發。示範省略。

    6.建立DML觸發器的注意事項

        DML觸發器中不能包含對基表的DQL查詢操作

            CREATE OR REPLACE TRIGGER tr_emp_sal

              BEFORE UPDATE OF sal ON emp --注意update 觸發器中使用了OF 關鍵字,當sal列發生變化時,tr_emp_sal被觸發

              FOR EACH ROW --使用行級觸發器

            DECLARE

              maxsal NUMBER(6, 2);

            BEGIN

              SELECT MAX(sal) INTO maxsal FROM emp;

              IF :new.sal > maxsal THEN

                RAISE_APPLICATION_ERROR(-20004, 'Beyond the highest salary');

              END IF;

            END;

            scott@ORCL> update emp set sal=5000 where empno=7369;

            update emp set sal=5000 where empno=7369

            *

            ERROR at line 1:

            ORA-04091: table SCOTT.EMP is mutating, trigger/function may not see

it

            ORA-06512: at "SCOTT.TR_EMP_SAL", line 4

            ORA-04088: error during execution of trigger 'SCOTT.TR_EMP_SAL'        

    7.使用DML觸發器實作資料完整性,參照完整性

        對于複雜的資料完整性,參照完整性,可以通過DML觸發器來完成普通限制所不能完成的任務

            CREATE OR REPLACE TRIGGER tr_del_upd_deptno

            AFTER DELETE OR UPDATE OF deptno ON dept

            FOR EACH ROW

              IF (UPDATING AND :old.deptno<>:new.deptno) THEN

                UPDATE emp SET deptno=:new.deptno WHERE deptno=:old.deptno;

              IF DELETING THEN

                DELETE FROM emp WHERE deptno=:old.deptno;

            scott@ORCL> update dept set deptno=50 where deptno=10;

            1 row updated.     

            scott@ORCL> select * from emp where deptno=10;

            no rows selected

            scott@ORCL> select * from emp where deptno=50;

                 EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM     DEPTNO

            ---------- ---------- --------- ---------- --------- ---------- ---------- ----------

                  7782 CLARK      MANAGER         7839 09-JUN-81       2750                    50

                  7839 KING       PRESIDENT            17-NOV-81       5300                    50

                  7934 MILLER     CLERK           7782 23-JAN-82       1600                    50

四、管理觸發器

    1.檢視系統中特定對象上的觸發器

        scott@ORCL> select trigger_name,status from user_triggers

          2  where table_name='EMP';

        TRIGGER_NAME                   STATUS

        ------------------------------ --------

        TR_BEFORE_UPDATE_EMP           ENABLED

        TR_AUDIT_EMP                   ENABLED

        TR_SEC_EMP                     ENABLED

        TR_SAL_CHANGE                  ENABLED

        TR_EMP_SAL                     ENABLED 

    2.檢視觸發器的源代碼

        scott@ORCL> col text format a65

        scott@ORCL> select line,text from user_source where name='TR_DEL_UPD_DEPTNO';

              LINE TEXT

        ---------- -----------------------------------------------------------------

                 1 TRIGGER tr_del_upd_deptno

                 2 AFTER DELETE OR UPDATE OF deptno ON dept

                 3 FOR EACH ROW

                 4 BEGIN

                 5   IF (UPDATING AND :old.deptno<>:new.deptno) THEN

                 6     UPDATE emp SET deptno=:new.deptno WHERE deptno=:old.deptno;

                 7   END IF;

                 8

                 9   IF DELETING THEN

                10     DELETE FROM emp WHERE deptno=:old.deptno;

                11   END IF;

                12 END;

    3.禁用觸發器

        當觸發器被禁用後,則表上的DML操作将不會觸發該觸發器,直到該觸發器被解除禁用(alter trigger trigger_name disable)

        scott@ORCL> alter trigger tr_emp_sal disable;

    4.啟用觸發器

        被禁用的觸發器可以被解除禁用(alter trigger trigger_name enable)

        scott@ORCL> alter trigger tr_emp_sal enable;

    5.禁用、啟用表上的所有觸發器

        scott@ORCL> alter table emp disable all triggers;

        scott@ORCL> alter table emp enable all triggers;

    6.重新編譯觸發器

        scott@ORCL> alter trigger tr_emp_sal compile;

    7.删除觸發器

        scott@ORCL> drop trigger tr_emp_sal;