ORACLE PL/SQL编程之八: 把触发器说透
本篇主要內容如下:
8.1?觸發器類型
8.1.1?DML觸發器
8.1.2?替代觸發器
8.1.3?系統觸發器
8.2?創建觸發器
8.2.1?觸發器觸發次序
8.2.2?創建DML觸發器
8.2.3?創建替代(INSTEAD OF)觸發器
8.2.3?創建系統事件觸發器
8.2.4?系統觸發器事件屬性
8.2.5?使用觸發器謂詞
8.2.6?重新編譯觸發器
8.3?刪除和使能觸發器
8.4?觸發器和數據字典
8.5???數據庫觸發器的應用舉例
?
?
觸發器是許多關系數據庫系統都提供的一項技術。在ORACLE系統里,觸發器類似過程和函數,都有聲明,執行和異常處理過程的PL/SQL塊。
8.1?觸發器類型
????觸發器在數據庫里以獨立的對象存儲,它與存儲過程和函數不同的是,存儲過程與函數需要用戶顯示調用才執行,而觸發器是由一個事件來啟動運行。即觸發器是當某個事件發生時自動地隱式運行。并且,觸發器不能接收參數。所以運行觸發器就叫觸發或點火(firing)。ORACLE事件指的是對數據庫的表進行的INSERT、UPDATE及DELETE操作或對視圖進行類似的操作。ORACLE將觸發器的功能擴展到了觸發ORACLE,如數據庫的啟動與關閉等。所以觸發器常用來完成由數據庫的完整性約束難以完成的復雜業務規則的約束,或用來監視對數據庫的各種操作,實現審計的功能。
?
8.1.1?DML觸發器
??? ORACLE可以在DML語句進行觸發,可以在DML操作前或操作后進行觸發,并且可以對每個行或語句操作上進行觸發。
?
8.1.2?替代觸發器
????由于在ORACLE里,不能直接對由兩個以上的表建立的視圖進行操作。所以給出了替代觸發器。它就是ORACLE 8專門為進行視圖操作的一種處理方法。
?
8.1.3?系統觸發器
ORACLE 8i?提供了第三種類型的觸發器叫系統觸發器。它可以在ORACLE數據庫系統的事件中進行觸發,如ORACLE系統的啟動與關閉等。
?
觸發器組成:?
l?????????觸發事件:引起觸發器被觸發的事件。?例如:DML語句(INSERT, UPDATE, DELETE語句對表或視圖執行數據處理操作)、DDL語句(如CREATE、ALTER、DROP語句在數據庫中創建、修改、刪除模式對象)、數據庫系統事件(如系統啟動或退出、異常錯誤)、用戶事件(如登錄或退出數據庫)。
l?????????觸發時間:即該TRIGGER?是在觸發事件發生之前(BEFORE)還是之后(AFTER)觸發,也就是觸發事件和該TRIGGER?的操作順序。
l?????????觸發操作:即該TRIGGER?被觸發之后的目的和意圖,正是觸發器本身要做的事情。?例如:PL/SQL?塊。
l?????????觸發對象:包括表、視圖、模式、數據庫。只有在這些對象上發生了符合觸發條件的觸發事件,才會執行觸發操作。
l?????????觸發條件:由WHEN子句指定一個邏輯表達式。只有當該表達式的值為TRUE時,遇到觸發事件才會自動執行觸發器,使其執行觸發操作。
l?????????觸發頻率:說明觸發器內定義的動作被執行的次數。即語句級(STATEMENT)觸發器和行級(ROW)觸發器。
語句級(STATEMENT)觸發器:是指當某觸發事件發生時,該觸發器只執行一次;
行級(ROW)觸發器:是指當某觸發事件發生時,對受到該操作影響的每一行數據,觸發器都單獨執行一次。
編寫觸發器時,需要注意以下幾點:
l?????????觸發器不接受參數。
l?????????一個表上最多可有12個觸發器,但同一時間、同一事件、同一類型的觸發器只能有一個。并各觸發器之間不能有矛盾。
l?????????在一個表上的觸發器越多,對在該表上的DML操作的性能影響就越大。
l????????觸發器最大為32KB。若確實需要,可以先建立過程,然后在觸發器中用CALL語句進行調用。
l?????????在觸發器的執行部分只能用DML語句(SELECT、INSERT、UPDATE、DELETE),不能使用DDL語句(CREATE、ALTER、DROP)。
l?????????觸發器中不能包含事務控制語句(COMMIT,ROLLBACK,SAVEPOINT)。因為觸發器是觸發語句的一部分,觸發語句被提交、回退時,觸發器也被提交、回退了。
l?????????在觸發器主體中調用的任何過程、函數,都不能使用事務控制語句。
l?????????在觸發器主體中不能申明任何Long和blob變量。新值new和舊值old也不能向表中的任何long和blob列。
l?????????不同類型的觸發器(如DML觸發器、INSTEAD OF觸發器、系統觸發器)的語法格式和作用有較大區別。
?
8.2?創建觸發器
創建觸發器的一般語法是:
?
CREATE[OR?REPLACE]TRIGGERtrigger_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?asparent}]
[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)時要執行的存儲過程,執行時根據其所依附的基表改動而自動觸發,因此與應用程序無關,用數據庫觸發器可以保證數據的一致性和完整性。
?
每張表最多可建立12?種類型的觸發器,它們是:
BEFORE INSERT
BEFORE INSERT FOR EACH ROW
AFTER INSERT
AFTER INSERT FOR EACH ROW
?
BEFORE UPDATE
BEFORE UPDATE FOR EACH ROW
AFTER UPDATE
AFTER UPDATE FOR EACH ROW
?
BEFORE DELETE
BEFORE DELETE FOR EACH ROW
AFTER DELETE
AFTER DELETE FOR EACH ROW
?
8.2.1?觸發器觸發次序
1.????????執行?BEFORE語句級觸發器;
2.????????對與受語句影響的每一行:
l?????????執行?BEFORE行級觸發器
l?????????執行?DML語句
l?????????執行?AFTER行級觸發器?
3.????????執行?AFTER語句級觸發器
?
8.2.2?創建DML觸發器
????觸發器名與過程名和包的名字不一樣,它是單獨的名字空間,因而觸發器名可以和表或過程有相同的名字,但在一個模式中觸發器名不能相同。
?
DML觸發器的限制
l?????????CREATE TRIGGER語句文本的字符長度不能超過32KB;
l?????????觸發器體內的SELECT?語句只能為SELECT … INTO …結構,或者為定義游標所使用的SELECT?語句。
l?????????觸發器中不能使用數據庫事務控制語句?COMMIT; ROLLBACK, SVAEPOINT?語句;
l?????????由觸發器所調用的過程或函數也不能使用數據庫事務控制語句;
l?????????觸發器中不能使用LONG, LONG RAW?類型;
l?????????觸發器內可以參照LOB?類型列的列值,但不能通過?:NEW?修改LOB列中的數據;
?
DML觸發器基本要點
l?????????觸發時機:指定觸發器的觸發時間。如果指定為BEFORE,則表示在執行DML操作之前觸發,以便防止某些錯誤操作發生或實現某些業務規則;如果指定為AFTER,則表示在執行DML操作之后觸發,以便記錄該操作或做某些事后處理。
l?????????觸發事件:引起觸發器被觸發的事件,即DML操作(INSERT、UPDATE、DELETE)。既可以是單個觸發事件,也可以是多個觸發事件的組合(只能使用OR邏輯組合,不能使用AND邏輯組合)。
l?????????條件謂詞:當在觸發器中包含多個觸發事件(INSERT、UPDATE、DELETE)的組合時,為了分別針對不同的事件進行不同的處理,需要使用ORACLE提供的如下條件謂詞。
1)。INSERTING:當觸發事件是INSERT時,取值為TRUE,否則為FALSE。
2)。UPDATING [(column_1,column_2,…,column_x)]:當觸發事件是UPDATE??????時,如果修改了column_x列,則取值為TRUE,否則為FALSE。其中column_x是可選的。
3)。DELETING:當觸發事件是DELETE時,則取值為TRUE,否則為FALSE。
解發對象:指定觸發器是創建在哪個表、視圖上。
l?????????觸發類型:是語句級還是行級觸發器。
l?????????觸發條件:由WHEN子句指定一個邏輯表達式,只允許在行級觸發器上指定觸發條件,指定UPDATING后面的列的列表。
?
問題:當觸發器被觸發時,要使用被插入、更新或刪除的記錄中的列值,有時要使用操作前、????????后列的值.
實現:??:NEW?修飾符訪問操作完成后列的值
?????? ?:OLD?修飾符訪問操作完成前列的值
?
?
| 特性 | INSERT | UPDATE | DELETE |
| OLD | NULL | 實際值 | 實際值 |
| NEW | 實際值 | 實際值 | NULL |
?
?
例1:?建立一個觸發器,?當職工表?emp?表被刪除一條記錄時,把被刪除記錄寫到職工表刪除日志表中去。
?
CREATETABLEemp_his?ASSELECT*FROMEMP?WHERE1=2;?CREATEORREPLACETRIGGERtr_del_emp?
???BEFORE?DELETE--指定觸發時機為刪除操作前觸發
ONscott.emp?
???FOREACH?ROW???--說明創建的是行級觸發器?
BEGIN
???--將修改前數據插入到日志記錄表?del_emp?,以供監督使用。
INSERTINTOemp_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;
DELETEemp?WHEREempno=7788;
DROPTABLEemp_his;
DROPTRIGGERdel_emp;
?
例2:限制對Departments表修改(包括INSERT,DELETE,UPDATE)的時間范圍,即不允許在非工作時間修改departments表。
?
CREATEORREPLACETRIGGERtr_dept_timeBEFORE?INSERTORDELETEORUPDATE
ONdepartments
BEGIN
?IF(TO_CHAR(sysdate,'DAY')?IN('星期六',?'星期日'))?OR(TO_CHAR(sysdate,?'HH24:MI')?NOTBETWEEN'08:30'AND'18:00')?THEN
?????RAISE_APPLICATION_ERROR(-20001,?'不是上班時間,不能修改departments表');
?ENDIF;
END;
?
例3:限定只對部門號為80的記錄進行行觸發器操作。
?
CREATEORREPLACETRIGGERtr_emp_sal_commBEFORE?UPDATEOFsalary,?commission_pct
???????ORDELETE
ONHR.employees
FOREACH?ROW
WHEN(old.department_id?=80)
BEGIN
?CASE
?????WHENUPDATING?('salary')?THEN
????????IF:NEW.salary?<:old.salary?THEN
???????????RAISE_APPLICATION_ERROR(-20001,?'部門80的人員的工資不能降');
????????ENDIF;
?????WHENUPDATING?('commission_pct')?THEN
????????IF:NEW.commission_pct?<:old.commission_pct?THEN
???????????RAISE_APPLICATION_ERROR(-20002,?'部門80的人員的獎金不能降');
????????ENDIF;
?????WHENDELETING?THEN
??????????RAISE_APPLICATION_ERROR(-20003,?'不能刪除部門80的人員記錄');
?????ENDCASE;
END;?
/*
實例:
UPDATE?employees?SET?salary?=?8000?WHERE?employee_id?=?177;
DELETE?FROM?employees?WHERE?employee_id?in?(177,170);
*/
?
?
?
例4:利用行觸發器實現級聯更新。在修改了主表regions中的region_id之后(AFTER),級聯的、自動的更新子表countries表中原來在該地區的國家的region_id。
?
?
CREATEORREPLACETRIGGERtr_reg_couAFTER?updateOFregion_id
ONregions
FOREACH?ROW
BEGIN
?DBMS_OUTPUT.PUT_LINE('舊的region_id值是'||:old.region_id
??????????????????||'、新的region_id值是'||:new.region_id);
?UPDATEcountries?SETregion_id?=:new.region_id
?WHEREregion_id?=:old.region_id;
END;
例5:在觸發器中調用過程。
?
CREATEORREPLACEPROCEDUREadd_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
?INSERTINTOjob_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);
ENDadd_job_history;
--創建觸發器調用存儲過程...
CREATEORREPLACETRIGGERupdate_job_history
?AFTER?UPDATEOFjob_id,?department_id?ONemployees
?FOREACH?ROW
BEGIN
?add_job_history(:old.employee_id,?:old.hire_date,?sysdate,
??????????????????:old.job_id,?:old.department_id);
END;
?
8.2.3?創建替代(INSTEAD OF)觸發器
?
創建觸發器的一般語法是:
?
CREATE[OR?REPLACE]TRIGGERtrigger_nameINSTEAD?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?asparent}]
[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?用于對視圖的DML觸發,由于視圖有可能是由多個表進行聯結(join)而成,因而并非是所有的聯結都是可更新的。但可以按照所需的方式執行更新,例如下面情況:
例1:
?
CREATEORREPLACEVIEWemp_view?ASSELECTdeptno,?count(*)?total_employeer,?sum(sal)?total_salary?
FROMemp?GROUPBYdeptno;
?
在此視圖中直接刪除是非法:
?
SQL>DELETEFROMemp_view?WHEREdeptno=10;DELETEFROMemp_view?WHEREdeptno=10
??????????
ERROR?位于第?1?行:
ORA-01732:?此視圖的數據操縱操作非法
?
但是我們可以創建INSTEAD_OF觸發器來為?DELETE?操作執行所需的處理,即刪除EMP表中所有基準行:
?
?
CREATEORREPLACETRIGGERemp_view_delete???INSTEAD?OFDELETEONemp_view?FOREACH?ROW
BEGIN
???DELETEFROMemp?WHEREdeptno=:old.deptno;
ENDemp_view_delete;?
DELETEFROMemp_view?WHEREdeptno=10;?
DROPTRIGGERemp_view_delete;
DROPVIEWemp_view;?
?
例2:創建復雜視圖,針對INSERT操作創建INSTEAD OF觸發器,向復雜視圖插入數據。
l?????????創建視圖:
?
CREATEORREPLACEFORCE?VIEW"HR"."V_REG_COU"?("R_ID",?"R_NAME",?"C_ID",?"C_NAME")AS
?SELECTr.region_id,
????r.region_name,
????c.country_id,
????c.country_name
?FROMregions?r,
????countries?c
?WHEREr.region_id?=c.region_id;
?
l?????????創建觸發器:
?
?
?
CREATEORREPLACETRIGGER"HR"."TR_I_O_REG_COU"?INSTEAD?OF?INSERTONv_reg_cou?FOREACH?ROW?DECLAREv_count?NUMBER;
BEGIN
?SELECTCOUNT(*)?INTOv_count?FROMregions?WHEREregion_id?=:new.r_id;
?IFv_count?=0THEN
????INSERTINTOregions
??????(region_id,?region_name
??????)?VALUES
??????(:new.r_id,?:new.r_name
??????);
?ENDIF;
?SELECTCOUNT(*)?INTOv_count?FROMcountries?WHEREcountry_id?=:new.c_id;
?IFv_count?=0THEN
????INSERT
????INTOcountries
??????(
????????country_id,
????????country_name,
????????region_id
??????)
??????VALUES
??????(
????????:new.c_id,
????????:new.c_name,
????????:new.r_id
??????);
?ENDIF;
END;
?
創建INSTEAD OF觸發器需要注意以下幾點:
l?????????只能被創建在視圖上,并且該視圖沒有指定WITH CHECK OPTION選項。
l?????????不能指定BEFORE?或?AFTER選項。
l?????????FOR EACH ROW子可是可選的,即INSTEAD OF觸發器只能在行級上觸發、或只能是行級觸發器,沒有必要指定。
l?????????沒有必要在針對一個表的視圖上創建INSTEAD OF觸發器,只要創建DML觸發器就可以了。
?
8.2.3?創建系統事件觸發器
??? ORACLE10G提供的系統事件觸發器可以在DDL或數據庫系統上被觸發。DDL指的是數據定義語言,如CREATE?、ALTER及DROP?等。而數據庫系統事件包括數據庫服務器的啟動或關閉,用戶的登錄與退出、數據庫服務錯誤等。創建系統觸發器的語法如下:?
創建觸發器的一般語法是:
?
CREATEORREPLACETRIGGER[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權限。
?
下面給出系統觸發器的種類和事件出現的時機(前或后):
?
| 事件 | 允許的時機 | 說明 |
| STARTUP | AFTER | 啟動數據庫實例之后觸發 |
| SHUTDOWN | BEFORE | 關閉數據庫實例之前觸發(非正常關閉不觸發) |
| SERVERERROR | AFTER | 數據庫服務器發生錯誤之后觸發 |
| LOGON | AFTER | 成功登錄連接到數據庫后觸發 |
| LOGOFF | BEFORE | 開始斷開數據庫連接之前觸發 |
| CREATE | BEFORE,AFTER | 在執行CREATE語句創建數據庫對象之前、之后觸發 |
| DROP | BEFORE,AFTER | 在執行DROP語句刪除數據庫對象之前、之后觸發 |
| ALTER | BEFORE,AFTER | 在執行ALTER語句更新數據庫對象之前、之后觸發 |
| DDL | BEFORE,AFTER | 在執行大多數DDL語句之前、之后觸發 |
| GRANT | BEFORE,AFTER | 執行GRANT語句授予權限之前、之后觸發 |
| REVOKE | BEFORE,AFTER | 執行REVOKE語句收權限之前、之后觸犯發 |
| RENAME | BEFORE,AFTER | 執行RENAME語句更改數據庫對象名稱之前、之后觸犯發 |
| AUDIT?/?NOAUDIT | BEFORE,AFTER | 執行AUDIT或NOAUDIT進行審計或停止審計之前、之后觸發 |
?
?
?
8.2.4?系統觸發器事件屬性
?
?
| 事件屬性\事件 | Startup/Shutdown | Servererror | Logon/Logoff | DDL | DML |
| 事件名稱 | ?* | ?* | ?* | ?* | * |
| 數據庫名稱 | ?* | ? | ? | ? | ? |
| 數據庫實例號 | ?* | ? | ? | ? | ? |
| 錯誤號 | ? | ?* | ? | ? | ? |
| 用戶名 | ? | ? | ?* | * | ? |
| 模式對象類型 | ? | ? | ? | ?* | * |
| 模式對象名稱 | ? | ? | ? | ?* | * |
| 列 | ? | ? | ? | ? | ?* |
?
?
除DML語句的列屬性外,其余事件屬性值可通過調用ORACLE定義的事件屬性函數來讀取。
?
| 函數名稱 | 數據類型 | 說????明 |
| Ora_sysevent | VARCHAR2(20) | 激活觸發器的事件名稱 |
| Instance_num | NUMBER | 數據庫實例名 |
| Ora_database_name | VARCHAR2(50) | 數據庫名稱 |
| Server_error(posi) | NUMBER | 錯誤信息棧中posi指定位置中的錯誤號 |
| ? ? Is_servererror(err_number) | ? ? BOOLEAN | 檢查err_number指定的錯誤號是否在錯誤信息棧中,如果在則返回TRUE,否則返回FALSE。在觸發器內調用此函數可以判斷是否發生指定的錯誤。 |
| Login_user | VARCHAR2(30) | 登陸或注銷的用戶名稱 |
| Dictionary_obj_type | VARCHAR2(20) | DDL語句所操作的數據庫對象類型 |
| Dictionary_obj_name | VARCHAR2(30) | DDL語句所操作的數據庫對象名稱 |
| Dictionary_obj_owner | VARCHAR2(30) | DDL語句所操作的數據庫對象所有者名稱 |
| Des_encrypted_password | VARCHAR2(2) | 正在創建或修改的經過DES算法加密的用戶口令 |
?
?
例1:創建觸發器,存放有關事件信息。
DESCora_syseventDESCora_login_user
--創建用于記錄事件用的表
CREATETABLEddl_event
(crt_date?timestampPRIMARYKEY,
?event_name?VARCHAR2(20),?
?user_nameVARCHAR2(10),
?obj_type?VARCHAR2(20),
?obj_name?VARCHAR2(20));
--創建觸犯發器
CREATEORREPLACETRIGGERtr_ddl
AFTER?DDL?ONSCHEMA
BEGIN
???INSERTINTOddl_event?VALUES
???(systimestamp,ora_sysevent,?ora_login_user,?
????ora_dict_obj_type,?ora_dict_obj_name);
ENDtr_ddl;
?
例2:創建登錄、退出觸發器。
?
CREATETABLElog_event(user_nameVARCHAR2(10),
?address?VARCHAR2(20),?
?logon_date?timestamp,
?logoff_date?timestamp);?
--創建登錄觸發器
CREATEORREPLACETRIGGERtr_logon
AFTER?LOGON?ONDATABASE
BEGIN
???INSERTINTOlog_event?(user_name,?address,?logon_date)
???VALUES(ora_login_user,?ora_client_ip_address,?systimestamp);
ENDtr_logon;
--創建退出觸發器
CREATEORREPLACETRIGGERtr_logoff
BEFORE?LOGOFF?ONDATABASE
BEGIN
???INSERTINTOlog_event?(user_name,?address,?logoff_date)
???VALUES(ora_login_user,?ora_client_ip_address,?systimestamp);
ENDtr_logoff;
?
8.2.5?使用觸發器謂詞
??? ORACLE?提供三個參數INSERTING, UPDATING, DELETING?用于判斷觸發了哪些操作。
?
| 謂詞 | 行為 |
| INSERTING | 如果觸發語句是?INSERT?語句,則為TRUE,否則為FALSE |
| UPDATING | 如果觸發語句是?UPDATE語句,則為TRUE,否則為FALSE |
| DELETING | 如果觸發語句是?DELETE?語句,則為TRUE,否則為FALSE |
?
?
8.2.6?重新編譯觸發器
如果在觸發器內調用其它函數或過程,當這些函數或過程被刪除或修改后,觸發器的狀態將被標識為無效。當DML語句激活一個無效觸發器時,ORACLE將重新編譯觸發器代碼,如果編譯時發現錯誤,這將導致DML語句執行失敗。
在PL/SQL程序中可以調用ALTER TRIGGER語句重新編譯已經創建的觸發器,格式為:???????????
ALTERTRIGGER[schema.]trigger_name?COMPILE?[DEBUG]???????其中:DEBUG?選項要器編譯器生成PL/SQL?程序條使其所使用的調試代碼。
8.3?刪除和使能觸發器
l?????????刪除觸發器:
DROPTRIGGERtrigger_name;當刪除其他用戶模式中的觸發器名稱,需要具有DROP ANY TRIGGER系統權限,當刪除建立在數據庫上的觸發器時,用戶需要具有ADMINISTER DATABASE TRIGGER系統權限。
此外,當刪除表或視圖時,建立在這些對象上的觸發器也隨之刪除。?
l?????????禁用或啟用觸發器
數據庫TRIGGER?的狀態:
有效狀態(ENABLE):當觸發事件發生時,處于有效狀態的數據庫觸發器TRIGGER?將被觸發。
無效狀態(DISABLE):當觸發事件發生時,處于無效狀態的數據庫觸發器TRIGGER?將不會被觸發,此時就跟沒有這個數據庫觸發器(TRIGGER)?一樣。
數據庫TRIGGER的這兩種狀態可以互相轉換。格式為:
?
ALTERTIGGER?trigger_name?[DISABLE?|?ENABLE?];--例:ALTER?TRIGGER?emp_view_delete?DISABLE;
???????????
??????????? ALTER TRIGGER語句一次只能改變一個觸發器的狀態,而ALTER TABLE語句則一次能夠改變與指定表相關的所有觸發器的使用狀態。格式為:?????????????
ALTERTABLE[schema.]table_name?{ENABLE|DISABLE}?ALLTRIGGERS;--例:使表EMP?上的所有TRIGGER?失效:
ALTERTABLEemp?DISABLE?ALLTRIGGERS;?
?
8.4?觸發器和數據字典
相關數據字典:USER_TRIGGERS、ALL_TRIGGERS、DBA_TRIGGERS?
SELECTTRIGGER_NAME,?TRIGGER_TYPE,?TRIGGERING_EVENT,?TABLE_OWNER,?BASE_OBJECT_TYPE,?REFERENCING_NAMES,
?STATUS,?ACTION_TYPE
?FROMuser_triggers;
?
8.5???數據庫觸發器的應用舉例
例1:創建一個DML語句級觸發器,當對emp表執行INSERT, UPDATE, DELETE?操作時,它自動更新dept_summary?表中的數據。由于在PL/SQL塊中不能直接調用DDL語句,所以,利用ORACLE內置包DBMS_UTILITY中的EXEC_DDL_STATEMENT過程,由它執行DDL語句創建觸發器。
?
CREATETABLEdept_summary(?Deptno?NUMBER(2),
?Sal_sum?NUMBER(9,?2),
?Emp_count?NUMBER);?
INSERTINTOdept_summary(deptno,?sal_sum,?emp_count)
?SELECTdeptno,?SUM(sal),?COUNT(*)?
FROMemp?
GROUPBYdeptno;
--創建一個PL/SQL過程disp_dept_summary
--在觸發器中調用該過程顯示dept_summary標中的數據。
CREATEORREPLACEPROCEDUREdisp_dept_summary
IS
?Rec?dept_summary%ROWTYPE;
?CURSORc1?ISSELECT*FROMdept_summary;
BEGIN
?OPENc1;
?FETCHc1?INTOREC;
?DBMS_OUTPUT.PUT_LINE('deptno????sal_sum????emp_count');
?DBMS_OUTPUT.PUT_LINE('-------------------------------------');
?WHILEc1%FOUND?LOOP
????DBMS_OUTPUT.PUT_LINE(RPAD(rec.deptno,?6)||
??????To_char(rec.sal_sum,?'$999,999.99')||
??????LPAD(rec.emp_count,?13));
????FETCHc1?INTOrec;
?ENDLOOP;
?CLOSEc1;
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;
?');
?INSERTINTOdept(deptno,?dname,?loc)?
?VALUES(90,?‘demo_dept’,?‘none_loc’);
?INSERTINTOemp(ename,?deptno,?empno,?sal)
?VALUES(USER,?90,?9999,?3000);
?DBMS_OUTPUT.PUT_LINE('插入后');
?Disp_dept_summary();
?UPDATEemp?SETsal=1000WHEREempno=9999;
?DBMS_OUTPUT.PUT_LINE('修改后');
?Disp_dept_summary();
?DELETEFROMemp?WHEREempno=9999;
?DELETEFROMdept?WHEREdeptno=90;
?DBMS_OUTPUT.PUT_LINE('刪除后');
?Disp_dept_summary();?
?DBMS_UTILITY.EXEC_DDL_STATEMENT(‘DROPTRIGGERtrig1’);
EXCEPTION
???WHENOTHERS?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;'
??);
??INSERTINTOdept(deptno,?dname,?loc)?
????VALUES(90,?'demo_dept',?'none_loc');
??INSERTINTOemp(ename,?deptno,?empno,?sal)
????VALUES(USER,?90,?9999,?3000);
??INSERTINTOemp(ename,?deptno,?empno,?sal)
????VALUES(USER,?90,?9998,?2000);
??DBMS_OUTPUT.PUT_LINE('插入后');
??Disp_dept_summary();
??UPDATEemp?SETsal?=sal*1.1WHEREdeptno=90;
??DBMS_OUTPUT.PUT_LINE('修改后');
??Disp_dept_summary();
??DELETEFROMemp?WHEREdeptno=90;
??DELETEFROMdept?WHEREdeptno=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
???WHENOTHERS?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;'
??);
??INSERTINTOdept(deptno,?dname,?loc)?
????VALUES(90,?'demo_dept',?'none_loc');
??INSERTINTOemp(ename,?deptno,?empno,?sal)
????VALUES(USER,?90,?9999,?3000);
??INSERTINTOemp(ename,?deptno,?empno,?sal)
????VALUES(USER,?90,?9998,?2000);
??DBMS_OUTPUT.PUT_LINE('插入后');
??Disp_dept_summary();
??UPDATEemp?SETsal?=sal*1.1WHEREdeptno=90;
??DBMS_OUTPUT.PUT_LINE('修改后');
??Disp_dept_summary();
??DELETEFROMemp?WHEREdeptno=90;
??DELETEFROMdept?WHEREdeptno=90;
??DBMS_OUTPUT.PUT_LINE('刪除后');
??Disp_dept_summary();
??DBMS_UTILITY.EXEC_DDL_STATEMENT('DROP?TRIGGER?trig2');
EXCEPTION
???WHENOTHERS?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表中插入數據…’’);
????????????????????INSERTINTOemp(empno,?ename)
????????????????????????VALUES(:n.empno,?:n.ename);
????????????????ELSE
????????????????????DBMS_OUTPUT.PUT_LINE(''編號為''||:n.empno||
??????????????????????''的人員已存在,插入操作失敗!'');
????????????????ENDIF;
????????????ENDIF;
????????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('DROPTRIGGERtrig3');
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;
CREATETABLEmydata(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
SELECTeventname,?eventdate,?obj_type,?obj_name,?obj_owner,?username,?Srv_error
??FROMeventlog;
DROPTRIGGERtrig4_ddl;
DROPTRIGGERtrig4_before;
DROPTRIGGERtrig4_after;
DROPTABLEeventlog;
DROPTABLEmydata;
?
8.6???數據庫觸發器的應用實例
用戶可以使用數據庫觸發器實現各種功能:
l?????????復雜的審計功能;
例:將EMP?表的變化情況記錄到AUDIT_TABLE和AUDIT_TABLE_VALUES中。
?
CREATETABLEaudit_table(????Audit_id?????NUMBER,
????User_nameVARCHAR2(20),
????Now_time?DATE,
????Terminal_name?VARCHAR2(10),
????Table_name?VARCHAR2(10),
????Action_name?VARCHAR2(10),
????Emp_id?NUMBER(4));
CREATETABLEaudit_table_val(
????Audit_id?NUMBER,
????Column_name?VARCHAR2(10),
????Old_val?NUMBER(7,2),
????New_val?NUMBER(7,2));
CREATESEQUENCE?audit_seq
????START?WITH1000
????INCREMENT?BY1
????NOMAXVALUE
????NOCYCLE?NOCACHE;
CREATEORREPLACETRIGGERaudit_emp
????AFTER?INSERTORUPDATEORDELETEONemp
????FOREACH?ROW
DECLARE
????Time_now?DATE;
????Terminal?CHAR(10);
BEGIN
????Time_now:=sysdate;
????Terminal:=USERENV('TERMINAL');
????IFINSERTING?THEN
????????INSERTINTOaudit_table
????VALUES(audit_seq.NEXTVAL,?user,?time_now,?
???????????terminal,?'EMP',?'INSERT',?:new.empno);
????ELSIF?DELETING?THEN
????????INSERTINTOaudit_table
????VALUES(audit_seq.NEXTVAL,?user,?time_now,?
???????????terminal,?'EMP',?'DELETE',?:old.empno);
????ELSE
????????INSERTINTOaudit_table
????VALUES(audit_seq.NEXTVAL,?user,?time_now,?
???????????terminal,?'EMP',?'UPDATE',?:old.empno);
????????IFUPDATING('SAL')?THEN
????????????INSERTINTOaudit_table_val
????????????????VALUES(audit_seq.CURRVAL,?'SAL',?:old.sal,?:new.sal);
????????ELSEUPDATING('DEPTNO')?
????????????INSERTINTOaudit_table_val
????????????????VALUES(audit_seq.CURRVAL,?'DEPTNO',?:old.deptno,?:new.deptno);
????????ENDIF;
????ENDIF;
END;
?
l?????????增強數據的完整性管理;
例:修改DEPT表的DEPTNO列時,同時把EMP表中相應的DEPTNO也作相應的修改;
?
CREATESEQUENCE?update_sequence?????INCREMENT?BY1
????START?WITH1000
????MAXVALUE?5000CYCLE;
ALTERTABLEemp
????ADDupdate_id?NUMBER;
CREATEORREPLACEPACKAGE?integritypackage?AS
????Updateseq?NUMBER;
ENDintegritypackage;
CREATEORREPLACEPACKAGE?BODY?integritypackage?AS
ENDintegritypackage;
CREATEORREPLACETRIGGERdept_cascade1
????BEFORE?UPDATEOFdeptno?ONdept
DECLARE
????DummyNUMBER;
BEGIN
????SELECTupdate_sequence.NEXTVAL?INTOdummyFROMdual;
????Integritypackage.updateseq:=dummy;
END;
CREATEORREPLACETRIGGERdept_cascade2
????AFTER?DELETEORUPDATEOFdeptno?ONdept
????FOREACH?ROW
BEGIN
????IFUPDATING?THEN
????????UPDATEemp?SETdeptno=:new.deptno,?
?????update_id=integritypackage.updateseq
????????WHEREemp.deptno=:old.deptno?ANDupdate_id?ISNULL;
????ENDIF;
????IFDELETING?THEN
????????DELETEFROMemp
????????????WHEREemp.deptno=:old.deptno;
????ENDIF;
END;
CREATEORREPLACETRIGGERdept_cascade3
????AFTER?UPDATEOFdeptno?ONdept?
BEGIN
????UPDATEemp?SETupdate_id=NULL
????????WHEREupdate_id=integritypackage.updateseq;
END;
SELECT*FROMEMP?ORDERBYDEPTNO;
UPDATEdept?SETdeptno=25WHEREdeptno=20;
?
l?????????幫助實現安全控制;
例:保證對EMP表的修改僅在工作日的工作時間;
?
CREATETABLEcompany_holidays(dayDATE);INSERTINTOcompany_holidays?
????VALUES(sysdate);
INSERTINTOcompany_holidays?
VALUES(TO_DATE('21-10月-01',?'DD-MON-YY'));
CREATEORREPLACETRIGGERemp_permit_change
????BEFORE?INSERTORDELETEORUPDATEONemp
DECLARE
????DummyNUMBER;
????Not_on_weekends?EXCEPTION;
????Not_on_holidays?EXCEPTION;
????Not_working_hours?EXCEPTION;
BEGIN
????/*check?for?weekends?*/
IFTO_CHAR(SYSDATE,?'DAY')?IN('星期六',?'星期日')?THEN
????RAISE?not_on_weekends;
ENDIF;
????/*check?for?company?holidays?*/
SELECTCOUNT(*)?INTOdummyFROMcompany_holidays
????WHERETRUNC(day)=TRUNC(SYSDATE);
IFdummy>0THEN
????RAISE?not_on_holidays;
ENDIF;
????/*check?for?work?hours(8:00?AM?to?18:00?PM?*/
IF(TO_CHAR(SYSDATE,'HH24')<8ORTO_CHAR(SYSDATE,?'HH24')>18)?THEN
??RAISE?not_working_hours;
ENDIF;
EXCEPTION
??WHENnot_on_weekends?THEN
????RAISE_APPLICATION_ERROR(-20324,?
'May?not?change?employee?table?during?the?weekends');?
??WHENnot_on_holidays?THEN
????RAISE_APPLICATION_ERROR(-20325,?
'May?not?change?employee?table?during?a?holiday');?
??WHENnot_working_hours?THEN
????RAISE_APPLICATION_ERROR(-20326,?
'May?not?change?employee?table?during?no_working?hours');?
END;
?
l?????????管理復雜的表復制;
l?????????防止非法的事務發生;
l?????????自動生成派生的列值;
幫助式顯復雜的商業管理。
?
轉載于:https://www.cnblogs.com/f204eng/archive/2013/04/11/3013835.html
總結
以上是生活随笔為你收集整理的ORACLE PL/SQL编程之八: 把触发器说透的全部內容,希望文章能夠幫你解決所遇到的問題。