Oracle 學習之觸發器
1. 觸發器簡介
觸發器是存儲在數據庫服務器中的程序單元,當一個表或一個視圖被改變,或者數據庫發生某些事件時,Oracle會自動觸發觸發器,並執行觸發器中的代碼。只有在觸發器中定義的事件發生時,觸發器才被觸發。觸發器是自動執行的代碼塊,和存儲過程的區別在於,用戶可以直接調用存儲過程,而不能直接調用觸發器。
觸發器在數據庫裏以獨立的對象存儲,它與存儲過程和函數不同的是,存儲過程與函數需要用戶顯示調用才執行,而觸發器是由一個事件來啟動運行。即觸發器是當某個事件發生時自動地隱式運行。並且,觸發器不能接收參數。所以運行觸發器就叫觸發或點火(firing)。ORACLE事件指的是對數據庫的表進行的INSERT、UPDATE及DELETE操作或對視圖進行類似的操作。ORACLE將觸發器的功能擴展到了觸發ORACLE,如數據庫的啟動與關閉等。所以觸發器常用來完成由數據庫的完整性約束難以完成的復雜業務規則的約束,或用來監視對數據庫的各種操作,實現審計的功能。
2. 觸發器種類
能夠觸發觸發器的事件有以下幾種:
1 DML操作(INSERT、UPDATE、DELETE)
2 DDL操作(CREATE、ALTER、DROP)
3 系統事件(數據庫的關閉與啟動等)
4 用戶事件(用戶的登陸等)
上述的這些語句都可以觸發觸發器。如果你想在這些事件發生時幹些別的事情,這個時候只需要定義對應的觸發器即可,在觸發器中完成你的工作。
觸發器是基於表、視圖、模式、數據庫的,於此,我們可以把觸發器分為下面的幾類:
2.1 行級觸發器和語句級觸發器
行級觸發器,即觸發機制是基於行的,當表中數據改變時,將觸發行級觸發器,改變一行數據,觸發一次;改變N行數據,就會觸發N次;
語句級觸發器是基於語句級的,當一條SQL語句改變數據時,無論這條SQL語句影響多少條記錄,語句級觸發器都只觸發一次。
2.2 BEFORE
和AFTER
觸發器
BEFORE:
表示在觸發語句運行前先運行“觸發動作”。
AFTER
表示觸發語句運行之後才運行“觸發動作”。
BEFORE
和AFTER
適用於行級觸發器和語句級觸發器。
2.3 復合觸發器
復合觸發器是表上的觸發器,它有4個時間點,可以讓我們針對不同的時間點指定不同的處理動作。這四個時間點分別如下:
-
- 在觸發語句執行前(BEFORE STATEMENT)
- 在觸發語句執行後(AFTER STATEMENT)
- 在每行記錄被修改之前(BEFORE EACH ROW)
- 在每行記錄被修改之後(AFTER EACH ROW)
觸發語句必須是DML,如果觸發語句沒有影響任何一行數據,並且也沒有指定BEFORE STATEMENT和AFTER STATEMENT兩個時間點,則觸發器也不會被觸發。
2.4 INSTEAD OF
觸發器
有的視圖,我們不能直接對其進行更新操作,但是我們可以在這種視圖上建立觸發器,利用觸發器對視圖的基表進行更新操作,這種類型的觸發器就叫做“INSTEAD OF觸發器”。
2.5 系統級觸發器
系統事件觸發器是基於數據庫系統的觸發器,系統事件觸發器與表、視圖沒有關系,系統事件包括數據庫啟動、關閉、服務器錯誤、數據庫角色改變等。當這些事件發生時,就會觸發系統事件觸發器。可以通過系統事件觸發器實現對數據庫的審計。
3. 觸發器的組成
觸發器一般由以下幾部分組成:
1. 觸發事件:引起觸發器被觸發的事件。 例如:DML語句(INSERT, UPDATE, DELETE語句對表或視圖執行數據處理操作)、DDL語句(如CREATE、ALTER、DROP語句在數據庫中創建、修改、刪除模式對象)、數據庫系統事件(如系統啟動或退出、異常錯誤)、用戶事件(如登錄或退出數據庫)。 2. 觸發時間:即該TRIGGER 是在觸發事件發生之前(BEFORE)還是之後(AFTER)觸發,也就是觸發事件和該TRIGGER 的操作順序。 3. 觸發操作:即該TRIGGER 被觸發之後的目的和意圖,正是觸發器本身要做的事情。 例如:PL/SQL 塊。 4. 觸發對象:包括表、視圖、模式、數據庫。只有在這些對象上發生了符合觸發條件的觸發事件,才會執行觸發操作。 5. 觸發條件:由WHEN子句指定一個邏輯表達式。只有當該表達式的值為TRUE時,遇到觸發事件才會自動執行觸發器,使其執行觸發操作。 6. 觸發頻率:說明觸發器內定義的動作被執行的次數。即語句級(STATEMENT)觸發器和行級(ROW)觸發器。
語句級(STATEMENT)觸發器:是指當某觸發事件發生時,該觸發器只執行一次;
行級(ROW)觸發器:是指當某觸發事件發生時,對受到該操作影響的每一行數據,觸發器都單獨執行一次。
4. 創建觸發器
創建觸發器的一般語法是:
CREATE [OR REPLACE] TRIGGER trigger_name {BEFORE | AFTER } {INSERT | DELETE | UPDATE [OF column [, column …]]} [OR {INSERT | DELETE | UPDATE [OF column [, column …]]}...] ON [schema.]table_name | [schema.]view_name [REFERENCING {OLD [AS] old | NEW [AS] new| PARENT as parent}] [FOR EACH ROW ] [WHEN condition] PL/SQL_BLOCK | CALL procedure_name;
其中:
BEFORE 和 AFTER指出觸發器的觸發時序分別為前觸發和後觸發方式,前觸發是在執行觸發事件之前觸發當前所創建的觸發器,後觸發是在執行觸發事件之後觸發當前所創建的觸發器。
FOR EACH ROW選項說明觸發器為行觸發器,行觸發器和語句觸發器的區別表現在:行觸發器要求當一個DML語句操作影響數據庫中的多行數據時,對於其中的每個數據行,只要它們符合觸發約束條件,均激活一次觸發器;而語句觸發器將整個語句操作作為觸發事件,當它符合約束條件時,激活一次觸發器。當省略 FOR EACH ROW 選項時,BEFORE 和 AFTER 觸發器為語句觸發器,而INSTEAD OF 觸發器則只能為行觸發器。
REFERENCING 子句說明相關名稱,在行觸發器的 PL/SQL 塊和 WHEN 子句中可以使用相關名稱參照當前的新、舊列值,默認的相關名稱分別為OLD和NEW。觸發器的PL/SQL塊中應用相關名稱時,必須在它們之前加冒號(:),但在WHEN子句中則不能加冒號。
WHEN 子句說明觸發約束條件。Condition 為一個邏輯表達時,其中必須包含相關名稱,而不能包含查詢語句,也不能調用PL/SQL 函數。WHEN 子句指定的觸發約束條件只能用在 BEFORE 和 AFTER 行觸發器中,不能用在INSTEAD OF 行觸發器和其它類型的觸發器中。
當一個基表被修改( INSERT, UPDATE, DELETE)時要執行的存儲過程,執行時根據其所依附的基表改動而自動觸發,因此與應用程序無關,用數據庫觸發器可以保證數據的一致性和完整性。
4.1 創建DML觸發器
DML觸發器基本要點
(1)觸發時機:指定觸發器的觸發時間。如果指定為BEFORE,則表示在執行DML操作之前觸發,以便防止某些錯誤操作發生或實現某些業務規則;如果指 定為AFTER,則表示在執行DML操作之後觸發,以便記錄該操作或做某些事後處理。
(2)觸發事件:引起觸發器被觸發的事件,即DML操作(INSERT、UPDATE、DELETE)。既可以是單個觸發事件,也可以是多個觸發事件的組合(只能使用O R邏輯組合,不能使用AND邏輯組合)。
(3)條件謂詞:當在觸發器中包含多個觸發事件(INSERT、UPDATE、DELETE)的組合時,為了分別針對不同的事件進行不同的處理,需要使用ORACLE提供 的如下條件謂詞。
(4)INSERTING:當觸發事件是INSERT時,取值為TRUE,否則為FALSE。
UPDATING [(column_1,column_2,…,column_x)]:當觸發事件是UPDATE 時,如果修改了column_x列,則取值為TRUE,否則為FALSE。其中column_x 是可選的。
(5)DELETING:當觸發事件是DELETE時,則取值為TRUE,否則為FALSE。
(6)觸發對象:指定觸發器是創建在哪個表、視圖上。
(7)觸發類型:是語句級還是行級觸發器。
(8)觸發條件:由WHEN子句指定一個邏輯表達式,只允許在行級觸發器上指定觸發條件,指定UPDATING後面的列的列表。
:NEW 修飾符訪問操作完成後列的值, :OLD 修飾符訪問操作完成前列的值
示例1:建立一個觸發器, 當職工表 emp 表被刪除一條記錄時,把被刪除記錄寫到職工表刪除日誌表中去。
CREATE TABLE emp_his AS SELECT * FROM EMP WHERE 1=2; CREATE OR REPLACE TRIGGER tr_del_emp BEFORE DELETE --指定觸發時機為刪除操作前觸發 ON scott.emp FOR EACH ROW --說明創建的是行級觸發器 BEGIN --將修改前數據插入到日誌記錄表 del_emp ,以供監督使用。 INSERT INTO emp_his(deptno , empno, ename , job ,mgr , sal , comm , hiredate ) VALUES( :old.deptno, :old.empno, :old.ename , :old.job,:old.mgr, :old.sal, :old.comm, :old.hiredate ); END;
示例2:限制對Departments表修改(包括INSERT,DELETE,UPDATE)的時間範圍,即不允許在非工作時間修改departments表
CREATE OR REPLACE TRIGGER tr_dept_time BEFORE INSERT OR DELETE OR UPDATE ON departments BEGIN IF (TO_CHAR(sysdate,‘DAY‘) IN (‘星期六‘, ‘星期日‘)) OR (TO_CHAR(sysdate, ‘HH24:MI‘) NOT BETWEEN ‘08:30‘ AND ‘18:00‘) THEN RAISE_APPLICATION_ERROR(-20001, ‘不是上班時間,不能修改departments表‘); END IF; END;
示例3:限定只對部門號為80的記錄進行行觸發器操作
CREATE OR REPLACE TRIGGER tr_emp_sal_comm BEFORE UPDATE OF salary, commission_pct OR DELETE ON HR.employees FOR EACH ROW WHEN (old.department_id = 80) BEGIN CASE WHEN UPDATING (‘salary‘) THEN IF :NEW.salary < :old.salary THEN RAISE_APPLICATION_ERROR(-20001, ‘部門80的人員的工資不能降‘); END IF; WHEN UPDATING (‘commission_pct‘) THEN IF :NEW.commission_pct < :old.commission_pct THEN RAISE_APPLICATION_ERROR(-20002, ‘部門80的人員的獎金不能降‘); END IF; WHEN DELETING THEN RAISE_APPLICATION_ERROR(-20003, ‘不能刪除部門80的人員記錄‘); END CASE; END;
示例4 利用行觸發器實現級聯更新,在修改了主表regions中的region_id之後(AFTER),級聯的、自動的更新子表countries表中原來在該地區的國家的region_id
CREATE OR REPLACE TRIGGER tr_reg_cou AFTER update OF region_id ON regions FOR EACH ROW BEGIN DBMS_OUTPUT.PUT_LINE(‘舊的region_id值是‘||:old.region_id ||‘、新的region_id值是‘||:new.region_id); UPDATE countries SET region_id = :new.region_id WHERE region_id = :old.region_id; END;
示例5 在觸發器中調用過程
CREATE OR REPLACE PROCEDURE add_job_history ( p_emp_id job_history.employee_id%type , p_start_date job_history.start_date%type , p_end_date job_history.end_date%type , p_job_id job_history.job_id%type , p_department_id job_history.department_id%type ) IS BEGIN INSERT INTO job_history (employee_id, start_date, end_date, job_id, department_id) VALUES(p_emp_id, p_start_date, p_end_date, p_job_id, p_department_id); END add_job_history; --創建觸發器調用存儲過程... CREATE OR REPLACE TRIGGER update_job_history AFTER UPDATE OF job_id, department_id ON employees FOR EACH ROW BEGIN add_job_history(:old.employee_id, :old.hire_date, sysdate, :old.job_id, :old.department_id); END
4.2 創建替代(INSTEAD OF)觸發器
創建觸發器的一般語法是:
CREATE [OR REPLACE] TRIGGER trigger_name INSTEAD OF {INSERT | DELETE | UPDATE [OF column [, column …]]} [OR {INSERT | DELETE | UPDATE [OF column [, column …]]}...] ON [schema.] view_name --只能定義在視圖上 [REFERENCING {OLD [AS] old | NEW [AS] new| PARENT as parent}] [FOR EACH ROW ] --因為INSTEAD OF觸發器只能在行級上觸發,所以沒有必要指定 [WHEN condition] PL/SQL_block | CALL procedure_name;
其中:
INSTEAD OF 選項使ORACLE激活觸發器,而不執行觸發事件。只能對視圖和對象視圖建立INSTEAD OF觸發器,而不能對表、模式和數據庫建立INSTEAD OF觸發器。
FOR EACH ROW 選項說明觸發器為行觸發器。行觸發器和語句觸發器的區別表現在:行觸發器要求當一個DML語句操走影響數據庫中的多行數據時,對於其中的每個數據行,只要它們符合觸發約束條件,均激活一次觸發器;而語句觸發器將整個語句操作作為觸發事件,當它符合約束條件時,激活一次觸發器。當省略FOR EACH ROW 選項時,BEFORE 和AFTER 觸發器為語句觸發器,而INSTEAD OF 觸發器則為行觸發器
REFERENCING 子句說明相關名稱,在行觸發器的PL/SQL塊和WHEN 子句中可以使用相關名稱參照當前的新、舊列值,默認的相關名稱分別為OLD和NEW。觸發器的PL/SQL塊中應用相關名稱時,必須在它們之前加冒號(:),但在WHEN子句中則不能加冒號。
WHEN 子句說明觸發約束條件。Condition 為一個邏輯表達時,其中必須包含相關名稱,而不能包含查詢語句,也不能調用PL/SQL 函數。WHEN 子句指定的觸發約束條件只能用在BEFORE 和AFTER 行觸發器中,不能用在INSTEAD OF 行觸發器和其它類型的觸發器中。
創建INSTEAD OF觸發器需要註意以下幾點:
- 只能被創建在視圖上,並且該視圖沒有指定WITH CHECK OPTION選項。
- 不能指定BEFORE 或 AFTER選項。
- FOR EACH ROW子可是可選的,即INSTEAD OF觸發器只能在行級上觸發、或只能是行級觸發器,沒有必要指定。
- 沒有必要在針對一個表的視圖上創建INSTEAD OF觸發器,只要創建DML觸發器就可以了。
4.3 創建系統事件觸發器
ORACLE10G提供的系統事件觸發器可以在DDL或數據庫系統上被觸發。DDL指的是數據定義語言,如CREATE 、ALTER及DROP 等。而數據庫系統事件包括數據庫服務器的啟動或關閉,用戶的登錄與退出、數據庫服務錯誤等。創建系統觸發器的語法如下:
CREATE OR REPLACE TRIGGER [sachema.]trigger_name {BEFORE|AFTER} {ddl_event_list | database_event_list} ON { DATABASE | [schema.]SCHEMA } [WHEN condition] PL/SQL_block | CALL procedure_name;
其中: ddl_event_list:一個或多個DDL 事件,事件間用 OR 分開;
database_event_list:一個或多個數據庫事件,事件間用 OR 分開;
系統事件觸發器既可以建立在一個模式上,又可以建立在整個數據庫上。當建立在模式(SCHEMA)之上時,只有模式所指定用戶的DDL操作和它們所導致的錯誤才激活觸發器, 默認時為當前用戶模式。當建立在數據庫(DATABASE)之上時,該數據庫所有用戶的DDL操作和他們所導致的錯誤,以及數據庫的啟動和關閉均可激活觸發器。要在數據庫之上建立觸發器時,要求用戶具有ADMINISTER DATABASE TRIGGER權限。
示例:創建登錄、退出觸發器
CREATE TABLE log_event (user_name VARCHAR2(10), address VARCHAR2(20), logon_date timestamp, logoff_date timestamp); --創建登錄觸發器 CREATE OR REPLACE TRIGGER tr_logon AFTER LOGON ON DATABASE BEGIN INSERT INTO log_event (user_name, address, logon_date) VALUES (ora_login_user, ora_client_ip_address, systimestamp); END tr_logon; --創建退出觸發器 CREATE OR REPLACE TRIGGER tr_logoff BEFORE LOGOFF ON DATABASE BEGIN INSERT INTO log_event (user_name, address, logoff_date) VALUES (ora_login_user, ora_client_ip_address, systimestamp); END tr_logoff;
4.4 使用觸發器謂詞
INSERTING |
如果觸發語句是 INSERT 語句,則為TRUE,否則為FALSE |
UPDATING |
如果觸發語句是 UPDATE語句,則為TRUE,否則為FALSE |
DELETING |
如果觸發語句是 DELETE 語句,則為TRUE,否則為FALSE |
4.5 數據庫觸發器的應用舉例
1. 創建一個DML語句級觸發器,當對emp表執行INSERT, UPDATE, DELETE 操作時,它自動更新 dept_summary 表中的數據。由於在PL/SQL塊中不能直接調用DDL語句,所以,利用ORACLE內置包DBMS_UTILITY中的EXEC_DDL_STATEMENT過程,由它執行DDL語句創建觸發器。
CREATE TABLE dept_summary( Deptno NUMBER(2), Sal_sum NUMBER(9, 2), Emp_count NUMBER); INSERT INTO dept_summary(deptno, sal_sum, emp_count) SELECT deptno, SUM(sal), COUNT(*) FROM emp GROUP BY deptno; --創建一個PL/SQL過程disp_dept_summary --在觸發器中調用該過程顯示dept_summary標中的數據。 CREATE OR REPLACE PROCEDURE disp_dept_summary IS Rec dept_summary%ROWTYPE; CURSOR c1 IS SELECT * FROM dept_summary; BEGIN OPEN c1; FETCH c1 INTO REC; DBMS_OUTPUT.PUT_LINE(‘deptno sal_sum emp_count‘); DBMS_OUTPUT.PUT_LINE(‘-------------------------------------‘); WHILE c1%FOUND LOOP DBMS_OUTPUT.PUT_LINE(RPAD(rec.deptno, 6)|| To_char(rec.sal_sum, ‘$999,999.99‘)|| LPAD(rec.emp_count, 13)); FETCH c1 INTO rec; END LOOP; CLOSE c1; END; BEGIN DBMS_OUTPUT.PUT_LINE(‘插入前‘); Disp_dept_summary(); DBMS_UTILITY.EXEC_DDL_STATEMENT(‘ CREATE OR REPLACE TRIGGER trig1 AFTER INSERT OR DELETE OR UPDATE OF sal ON emp BEGIN DBMS_OUTPUT.PUT_LINE(‘‘正在執行trig1 觸發器…‘‘); DELETE FROM dept_summary; INSERT INTO dept_summary(deptno, sal_sum, emp_count) SELECT deptno, SUM(sal), COUNT(*) FROM emp GROUP BY deptno; END; ‘); INSERT INTO dept(deptno, dname, loc) VALUES(90, ‘demo_dept’, ‘none_loc’); INSERT INTO emp(ename, deptno, empno, sal) VALUES(USER, 90, 9999, 3000); DBMS_OUTPUT.PUT_LINE(‘插入後‘); Disp_dept_summary(); UPDATE emp SET sal=1000 WHERE empno=9999; DBMS_OUTPUT.PUT_LINE(‘修改後‘); Disp_dept_summary(); DELETE FROM emp WHERE empno=9999; DELETE FROM dept WHERE deptno=90; DBMS_OUTPUT.PUT_LINE(‘刪除後‘); Disp_dept_summary(); DBMS_UTILITY.EXEC_DDL_STATEMENT(‘DROP TRIGGER trig1’); EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE(SQLCODE||‘---‘||SQLERRM); END;
例2:創建DML語句行級觸發器。當對emp表執行INSERT, UPDATE, DELETE 操作時,它自動更新dept_summary 表中的數據。由於在PL/SQL塊中不能直接調用DDL語句,所以,利用ORACLE內置包DBMS_UTILITY中的EXEC_DDL_STATEMENT過程,由它執行DDL語句創建觸發器。
BEGIN DBMS_OUTPUT.PUT_LINE(‘插入前‘); Disp_dept_summary(); DBMS_UTILITY.EXEC_DDL_STATEMENT( ‘CREATE OR REPLACE TRIGGER trig2_update AFTER UPDATE OF sal ON emp REFERENCING OLD AS old_emp NEW AS new_emp FOR EACH ROW WHEN (old_emp.sal != new_emp.sal) BEGIN DBMS_OUTPUT.PUT_LINE(‘‘正在執行trig2_update 觸發器…‘‘); DBMS_OUTPUT.PUT_LINE(‘‘sal 舊值:‘‘|| :old_emp.sal); DBMS_OUTPUT.PUT_LINE(‘‘sal 新值:‘‘|| :new_emp.sal); UPDATE dept_summary SET sal_sum=sal_sum + :new_emp.sal - :old_emp.sal WHERE deptno = :new_emp.deptno; END;‘ ); DBMS_UTILITY.EXEC_DDL_STATEMENT( ‘CREATE OR REPLACE TRIGGER trig2_insert AFTER INSERT ON emp REFERENCING NEW AS new_emp FOR EACH ROW DECLARE I NUMBER; BEGIN DBMS_OUTPUT.PUT_LINE(‘‘正在執行trig2_insert 觸發器…‘‘); SELECT COUNT(*) INTO I FROM dept_summary WHERE deptno = :new_emp.deptno; IF I > 0 THEN UPDATE dept_summary SET sal_sum=sal_sum+:new_emp.sal, Emp_count=emp_count+1 WHERE deptno = :new_emp.deptno; ELSE INSERT INTO dept_summary VALUES (:new_emp.deptno, :new_emp.sal, 1); END IF; END;‘ ); DBMS_UTILITY.EXEC_DDL_STATEMENT( ‘CREATE OR REPLACE TRIGGER trig2_delete AFTER DELETE ON emp REFERENCING OLD AS old_emp FOR EACH ROW DECLARE I NUMBER; BEGIN DBMS_OUTPUT.PUT_LINE(‘‘正在執行trig2_delete 觸發器…‘‘); SELECT emp_count INTO I FROM dept_summary WHERE deptno = :old_emp.deptno; IF I >1 THEN UPDATE dept_summary SET sal_sum=sal_sum - :old_emp.sal, Emp_count=emp_count - 1 WHERE deptno = :old_emp.deptno; ELSE DELETE FROM dept_summary WHERE deptno = :old_emp.deptno; END IF; END;‘ ); INSERT INTO dept(deptno, dname, loc) VALUES(90, ‘demo_dept‘, ‘none_loc‘); INSERT INTO emp(ename, deptno, empno, sal) VALUES(USER, 90, 9999, 3000); INSERT INTO emp(ename, deptno, empno, sal) VALUES(USER, 90, 9998, 2000); DBMS_OUTPUT.PUT_LINE(‘插入後‘); Disp_dept_summary(); UPDATE emp SET sal = sal*1.1 WHERE deptno=90; DBMS_OUTPUT.PUT_LINE(‘修改後‘); Disp_dept_summary(); DELETE FROM emp WHERE deptno=90; DELETE FROM dept WHERE deptno=90; DBMS_OUTPUT.PUT_LINE(‘刪除後‘); Disp_dept_summary(); DBMS_UTILITY.EXEC_DDL_STATEMENT(‘DROP TRIGGER trig2_update‘); DBMS_UTILITY.EXEC_DDL_STATEMENT(‘DROP TRIGGER trig2_insert‘); DBMS_UTILITY.EXEC_DDL_STATEMENT(‘DROP TRIGGER trig2_delete‘); EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE(SQLCODE||‘---‘||SQLERRM); END;
例3:利用ORACLE提供的條件謂詞INSERTING、UPDATING和DELETING創建與例2具有相同功能的觸發器。
BEGIN DBMS_OUTPUT.PUT_LINE(‘插入前‘); Disp_dept_summary(); DBMS_UTILITY.EXEC_DDL_STATEMENT( ‘CREATE OR REPLACE TRIGGER trig2 AFTER INSERT OR DELETE OR UPDATE OF sal ON emp REFERENCING OLD AS old_emp NEW AS new_emp FOR EACH ROW DECLARE I NUMBER; BEGIN IF UPDATING AND :old_emp.sal != :new_emp.sal THEN DBMS_OUTPUT.PUT_LINE(‘‘正在執行trig2 觸發器…‘‘); DBMS_OUTPUT.PUT_LINE(‘‘sal 舊值:‘‘|| :old_emp.sal); DBMS_OUTPUT.PUT_LINE(‘‘sal 新值:‘‘|| :new_emp.sal); UPDATE dept_summary SET sal_sum=sal_sum + :new_emp.sal - :old_emp.sal WHERE deptno = :new_emp.deptno; ELSIF INSERTING THEN DBMS_OUTPUT.PUT_LINE(‘‘正在執行trig2觸發器…‘‘); SELECT COUNT(*) INTO I FROM dept_summary WHERE deptno = :new_emp.deptno; IF I > 0 THEN UPDATE dept_summary SET sal_sum=sal_sum+:new_emp.sal, Emp_count=emp_count+1 WHERE deptno = :new_emp.deptno; ELSE INSERT INTO dept_summary VALUES (:new_emp.deptno, :new_emp.sal, 1); END IF; ELSE DBMS_OUTPUT.PUT_LINE(‘‘正在執行trig2觸發器…‘‘); SELECT emp_count INTO I FROM dept_summary WHERE deptno = :old_emp.deptno; IF I > 1 THEN UPDATE dept_summary SET sal_sum=sal_sum - :old_emp.sal, Emp_count=emp_count - 1 WHERE deptno = :old_emp.deptno; ELSE DELETE FROM dept_summary WHERE deptno = :old_emp.deptno; END IF; END IF; END;‘ ); INSERT INTO dept(deptno, dname, loc) VALUES(90, ‘demo_dept‘, ‘none_loc‘); INSERT INTO emp(ename, deptno, empno, sal) VALUES(USER, 90, 9999, 3000); INSERT INTO emp(ename, deptno, empno, sal) VALUES(USER, 90, 9998, 2000); DBMS_OUTPUT.PUT_LINE(‘插入後‘); Disp_dept_summary(); UPDATE emp SET sal = sal*1.1 WHERE deptno=90; DBMS_OUTPUT.PUT_LINE(‘修改後‘); Disp_dept_summary(); DELETE FROM emp WHERE deptno=90; DELETE FROM dept WHERE deptno=90; DBMS_OUTPUT.PUT_LINE(‘刪除後‘); Disp_dept_summary(); DBMS_UTILITY.EXEC_DDL_STATEMENT(‘DROP TRIGGER trig2‘); EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE(SQLCODE||‘---‘||SQLERRM); END;
例4:創建INSTEAD OF 觸發器。首先創建一個視圖myview, 由於該視圖是復合查詢所產生的視圖,所以不能執行DML語句。根據用戶對視圖所插入的數據判斷需要將數據插入到哪個視圖基表中,然後對該基表執行插入操作。
DECLARE No NUMBER; Name VARCHAR2(20); BEGIN DBMS_UTILITY.EXEC_DDL_STATEMENT(‘ CREATE OR REPLACE VIEW myview AS SELECT empno, ename, ‘‘E‘‘ type FROM emp UNION SELECT dept.deptno, dname, ‘‘D‘‘ FROM dept ‘); -- 創建INSTEAD OF 觸發器trigger3; DBMS_UTILITY.EXEC_DDL_STATEMENT(‘ CREATE OR REPLACE TRIGGER trig3 INSTEAD OF INSERT ON myview REFERENCING NEW n FOR EACH ROW DECLARE Rows INTEGER; BEGIN DBMS_OUTPUT.PUT_LINE(‘‘正在執行trig3觸發器…‘‘); IF :n.type = ‘‘D‘‘ THEN SELECT COUNT(*) INTO rows FROM dept WHERE deptno = :n.empno; IF rows = 0 THEN DBMS_OUTPUT.PUT_LINE(‘‘向dept表中插入數據…‘‘); INSERT INTO dept(deptno, dname, loc) VALUES (:n.empno, :n.ename, ‘‘none’’); ELSE DBMS_OUTPUT.PUT_LINE(‘‘編號為‘‘|| :n.empno|| ‘‘的部門已存在,插入操作失敗!‘‘); END IF; ELSE SELECT COUNT(*) INTO rows FROM emp WHERE empno = :n.empno; IF rows = 0 THEN DBMS_OUTPUT.PUT_LINE(‘’向emp表中插入數據…’’); INSERT INTO emp(empno, ename) VALUES(:n.empno, :n.ename); ELSE DBMS_OUTPUT.PUT_LINE(‘‘編號為‘‘|| :n.empno|| ‘‘的人員已存在,插入操作失敗!‘‘); END IF; END IF; END; ‘); INSERT INTO myview VALUES (70, ‘demo‘, ‘D‘); INSERT INTO myview VALUES (9999, USER, ‘E‘); SELECT deptno, dname INTO no, name FROM dept WHERE deptno=70; DBMS_OUTPUT.PUT_LINE(‘員工編號:‘||TO_CHAR(no)||‘姓名:‘||name); SELECT empno, ename INTO no, name FROM emp WHERE empno=9999; DBMS_OUTPUT.PUT_LINE(‘部門編號:‘||TO_CHAR(no)||‘姓名:‘||name); DELETE FROM emp WHERE empno=9999; DELETE FROM dept WHERE deptno=70; DBMS_UTILITY.EXEC_DDL_STATEMENT(‘DROP TRIGGER trig3‘); END;
例5:利用ORACLE事件屬性函數,創建一個系統事件觸發器。首先創建一個事件日誌表eventlog,由它存儲用戶在當前數據庫中所創建的數據庫對象,以及用戶的登陸和註銷、數據庫的啟動和關閉等事件,之後創建trig4_ddl、trig4_before和trig4_after觸發器,它們調用事件屬性函數將各個事件記錄到eventlog數據表中。
BEGIN -- 創建用於記錄事件日誌的數據表 DBMS_UTILITY.EXEC_DDL_STATEMENT(‘ CREATE TABLE eventlog( Eventname VARCHAR2(20) NOT NULL, Eventdate date default sysdate, Inst_num NUMBER NULL, Db_name VARCHAR2(50) NULL, Srv_error NUMBER NULL, Username VARCHAR2(30) NULL, Obj_type VARCHAR2(20) NULL, Obj_name VARCHAR2(30) NULL, Obj_owner VARCHAR2(30) NULL ) ‘); -- 創建DDL觸發器trig4_ddl DBMS_UTILITY.EXEC_DDL_STATEMENT(‘ CREATE OR REPLACE TRIGGER trig4_ddl AFTER CREATE OR ALTER OR DROP ON DATABASE DECLARE Event VARCHAR2(20); Typ VARCHAR2(20); Name VARCHAR2(30); Owner VARCHAR2(30); BEGIN -- 讀取DDL事件屬性 Event := SYSEVENT; Typ := DICTIONARY_OBJ_TYPE; Name := DICTIONARY_OBJ_NAME; Owner := DICTIONARY_OBJ_OWNER; --將事件屬性插入到事件日誌表中 INSERT INTO scott.eventlog(eventname, obj_type, obj_name, obj_owner) VALUES(event, typ, name, owner); END; ‘); -- 創建LOGON、STARTUP和SERVERERROR 事件觸發器 DBMS_UTILITY.EXEC_DDL_STATEMENT(‘ CREATE OR REPLACE TRIGGER trig4_after AFTER LOGON OR STARTUP OR SERVERERROR ON DATABASE DECLARE Event VARCHAR2(20); Instance NUMBER; Err_num NUMBER; Dbname VARCHAR2(50); User VARCHAR2(30); BEGIN Event := SYSEVENT; IF event = ‘‘LOGON‘‘ THEN User := LOGIN_USER; INSERT INTO eventlog(eventname, username) VALUES(event, user); ELSIF event = ‘‘SERVERERROR‘‘ THEN Err_num := SERVER_ERROR(1); INSERT INTO eventlog(eventname, srv_error) VALUES(event, err_num); ELSE Instance := INSTANCE_NUM; Dbname := DATABASE_NAME; INSERT INTO eventlog(eventname, inst_num, db_name) VALUES(event, instance, dbname); END IF; END; ‘); -- 創建LOGOFF和SHUTDOWN 事件觸發器 DBMS_UTILITY.EXEC_DDL_STATEMENT(‘ CREATE OR REPLACE TRIGGER trig4_before BEFORE LOGOFF OR SHUTDOWN ON DATABASE DECLARE Event VARCHAR2(20); Instance NUMBER; Dbname VARCHAR2(50); User VARCHAR2(30); BEGIN Event := SYSEVENT; IF event = ‘‘LOGOFF‘‘ THEN User := LOGIN_USER; INSERT INTO eventlog(eventname, username) VALUES(event, user); ELSE Instance := INSTANCE_NUM; Dbname := DATABASE_NAME; INSERT INTO eventlog(eventname, inst_num, db_name) VALUES(event, instance, dbname); END IF; END; ‘); END; CREATE TABLE mydata(mydate NUMBER); CONNECT SCOTT/TIGER COL eventname FORMAT A10 COL eventdate FORMAT A12 COL username FORMAT A10 COL obj_type FORMAT A15 COL obj_name FORMAT A15 COL obj_owner FORMAT A10 SELECT eventname, eventdate, obj_type, obj_name, obj_owner, username, Srv_error FROM eventlog; DROP TRIGGER trig4_ddl; DROP TRIGGER trig4_before; DROP TRIGGER trig4_after; DROP TABLE eventlog; DROP TABLE mydata;
4.6 數據庫觸發器的應用實例
用戶可以使用數據庫觸發器實現各種功能:復雜的審計功能;例:將EMP 表的變化情況記錄到AUDIT_TABLE和AUDIT_TABLE_VALUES中。
CREATE TABLE audit_table( Audit_id NUMBER, User_name VARCHAR2(20), Now_time DATE, Terminal_name VARCHAR2(10), Table_name VARCHAR2(10), Action_name VARCHAR2(10), Emp_id NUMBER(4)); CREATE TABLE audit_table_val( Audit_id NUMBER, Column_name VARCHAR2(10), Old_val NUMBER(7,2), New_val NUMBER(7,2)); CREATE SEQUENCE audit_seq START WITH 1000 INCREMENT BY 1 NOMAXVALUE NOCYCLE NOCACHE; 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 Time_now:=sysdate; Terminal:=USERENV(‘TERMINAL‘); IF INSERTING THEN INSERT INTO audit_table VALUES(audit_seq.NEXTVAL, user, time_now, terminal, ‘EMP‘, ‘INSERT‘, :new.empno); ELSIF DELETING THEN INSERT INTO audit_table VALUES(audit_seq.NEXTVAL, user, time_now, terminal, ‘EMP‘, ‘DELETE‘, :old.empno); ELSE INSERT INTO audit_table VALUES(audit_seq.NEXTVAL, user, time_now, terminal, ‘EMP‘, ‘UPDATE‘, :old.empno); IF UPDATING(‘SAL‘) THEN INSERT INTO audit_table_val VALUES(audit_seq.CURRVAL, ‘SAL‘, :old.sal, :new.sal); ELSE UPDATING(‘DEPTNO‘) INSERT INTO audit_table_val VALUES(audit_seq.CURRVAL, ‘DEPTNO‘, :old.deptno, :new.deptno); END IF; END IF; END;
示例2 增強數據的完整性管理
例:修改DEPT表的DEPTNO列時,同時把EMP表中相應的DEPTNO也作相應的修改;
CREATE SEQUENCE update_sequence INCREMENT BY 1 START WITH 1000 MAXVALUE 5000 CYCLE; ALTER TABLE emp ADD update_id NUMBER; CREATE OR REPLACE PACKAGE integritypackage AS Updateseq NUMBER; END integritypackage; CREATE OR REPLACE PACKAGE BODY integritypackage AS END integritypackage; CREATE OR REPLACE TRIGGER dept_cascade1 BEFORE UPDATE OF deptno ON dept DECLARE Dummy NUMBER; 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 FOR EACH ROW BEGIN IF UPDATING THEN UPDATE emp SET deptno=:new.deptno, update_id=integritypackage.updateseq WHERE emp.deptno=:old.deptno AND update_id IS NULL; END IF; IF DELETING THEN 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; SELECT * FROM EMP ORDER BY DEPTNO; UPDATE dept SET deptno=25 WHERE deptno=20;
轉自:http://www.cnblogs.com/huyong/archive/2011/04/27/2030466.html#undefined 該博主特別無私,分享了許多有用的東西,希望大家可以多去看看
Oracle 學習之觸發器