天天看點

PL/SQL 觸發器

觸發器的一個明顯的特性就是不能被顯式地調用,當觸發事件發生時就會隐式地執行該觸發器,而且觸發器是不接收參數的。

觸發器本身就是一個命名的語句塊。

1.完成表的變更校驗

2.自動資料庫維護

3.控制資料庫管理活動

觸發器組成部分:

1.觸發器觸發的事件

2.觸發事件所在的對象

3.觸發器觸發的條件

4.觸發器被觸發時所要執行的語句塊,或稱觸發器體,是一個包括SQL語句和PL/SQL語句的過程調用或PLSQL塊,或者是被封裝在PLSQL塊中的java程式.

觸發器定義示例

create or replace tregger t_verifysalary

before update on emp for each row when(new.sal>old.sal)

declare

v_sal number;

begin

if updating('sal') then

v_sal:=:NEW.sal - :OLD.sal;

delete from emp_history where empno=:OLD.empno;

insert into emp_history values (:OLD.empno,:OLD.ename,:OLD.job,:OLD.mgr,:OLD.hiredate,:OLD.sal,:OLD.comm,:OLD.deptno);

update emp_history set sal=v_sal where empno=:NEW.empno;

end if;

end;

觸發器的分類

1.行觸發器與語句觸發器:行觸發器會對資料庫表中的每一行觸發一次觸發器代碼,語句觸發器則僅觸發一次,與語句所影響的行數無關。

2.before觸發器與alfter觸發器:是指與觸發時機相關的觸發器。

3.instead of觸發器:又稱為替代觸發器,是指不直接執行觸發語句,一般用在視圖更新的場合。

4.系統事件觸發器與使用者事件觸發器:在發生系統級的事件時。比如資料庫啟動,伺服器錯誤消息時間觸發時。

5.DML觸發器

6.系統觸發器:對資料庫執行個體或某個使用者模式進行操作時的觸發器,是以可以定義資料庫系統觸發器和使用者觸發器

7.替代觸發器:當對視圖進行操作時定義的觸發器。

DML觸發器

1.單行觸發器執行順序

當在某一行上定義了多個觸發器時

1.before語句觸發器

2.before行級觸發器

3.執行DML

4.after行級觸發器

5.after語句觸發器

2.多行觸發器執行順序

如果觸發器影響到多行,那麼在每一行上都要執行一次觸發器語句,假定觸發器影響到兩行,則其執行順序

2.第一行的before行觸發器

3.第一行執行DML

4.第一行after行級觸發器

5.第二行before行級觸發器

6.第二行執行DML

7.第二行alfter行級觸發器

8.after語句觸發器

在使用update作為觸發行為時,還可以使用update of 來指定一個或多個字段

before update of empno,ename,sal on emp

示例記錄日志觸發器

建立一個日志記錄表,當使用者對emp表進行新增,修改或删除時,會将修改記錄記錄到這個日志表中,以便知道對emp表的更改曆史記錄。

create table emp_log(

log_id number,

log_action varchar2(100),

log_date DATE,

empno number(4),

ename varchar2(10),

job varchar2(18),

mgr number(4),

hiredate date,

sal number(7,2),

comm number(7,2),

deptno number(2)

);

create or replace trigger t_emp_log

after insert or delete or update on emp for each row

if inserting then

insert into emp_log values(emp_seq.nextval,'INSERT',sysdate,:new.empno,:new.ename,:new.job,:new.mgr,:new.hiredate,:new.sal,:new.comm,:new.deptno);

insert into emp_log values(emp_seq.nextval,'UPDATE_NEW',sysdate,:new.empno,:new.ename,:new.job,:new.mgr,:new.hiredate,:new.sal,:new.comm,:new.deptno);

insert into emp_log values(emp_seq.currval,'UPDATE_OLD',sysdate,:old.empno,:old.ename,:old.job,:old.mgr,:old.hiredate,:old.sal,:old.comm,:old.deptno);

elsif deleting then

insert into emp_log values(emp_seq.nextval,'DELETE',:old.empno,:old.ename,:old.job,:old.mgr,:old.hiredate,:old.sal,:old.comm,:old.deptno);

使用語句觸發器

如果在建立觸發器時,不指定for each row子句,那麼建立的觸發器就是語句觸發器,否則為行觸發器。語句觸發器每次觸發器觸發時僅執行一次.

1.使用before語句觸發器

使用語句觸發器限制修改

對emp表的更改隻能在正常工作日的8:30~18:00之内,不再這個時間的修改都不能進行

create or replace trigger t_verify_emptime

before insert or delete or update on emp

if(TO_CHAR(sysdate,'DAY') IN ('星期六','星期天')) or (TO_CHAR(sysdate,'HH24:MI') NOT BETWEEN '08:30' ADN '18:00')

then

raise_application_error(-20001,'不能在非常時間段内操縱emp表');

2.使用after語句觸發器

after語句觸發器在所有的觸發器都執行完成之後,最後被觸發,在這個階段可以進行一些審計工作,比如統計自觸發器添加到現在以來所執行過的dml語句的次數和最後執行的時間,以便于根據這個結果進行性能的分析。

create table audit_table(

table_name varchar2(20),

ins_count int,

udp_count int,

del_count int,

start_time date,

end_time date

create or replace trigger t_audit_emp

after insert or update or delete on emp

v_temp int;

select count(*) into v_temp from audit_table where table_name='EMP';

if v_temp=0

insert into audit_table values('EMP',0,0,0,SYSDATE,NULL);

case

when inserting then

update audit_table set ins_count=ins_count+1,end_time=sysdate where table_name='EMP';

when updating then

update audit_table set udp_count=udp_count+1,end_time=sysdate where table_name='EMP';

when deleting  then

update audit_table set del_count=del_count+1,end_time=sysdate where table_name='EMP';

end case;

使用OLD和NEW謂詞

1.當在insert語句上激發觸發器時,OLD結構是不包含任何值的

2.當在update語句上激發觸發器時,OLD和NEW結構都是具有值,OLD包含在更新之前記錄的值,NEW包含了在更新之後記錄的值。

3.當在DELETE語句上激發觸發器時,NEW結構不包含任何值,OLD結構包含已經被删除的記錄

4.NEW和OLD謂詞也包含了rowid僞列,這個僞列在OLD和NEW結構中具有相同的值。

5.不能更改OLD結構的值,如果這樣做會觸發ORA-04085錯誤。但是可以修改NEW結構的值

6.在觸發器内部,不能将NEW或OLD結構作為一個記錄參數傳遞給過程或函數,僅能傳遞單個的字段

7.當在匿名塊或觸發器内部使用NEW和OLD謂詞時,必須要在前面加上冒号。

8.在NEW和OLD結構中不能進行記錄級别的操作,比如直接為記錄指派是非法的。

示例

create table emp_data

(

emp_id int,

empno number,

ename varchar2(20)

create table emp_data_his

emp_id int,

create or replace trigger t_emp_data

before insert on emp_data for each row

emp_rec emp_data%ROWTYPE;

select emp_seq.nextval into :NEW.emp_id from dual;

emp_rec.emp_id:=:NEW.emp_id;

emp_rec.empno:=:NEW.empno;

emp_rec.ename:=:NEW.ename;

insert into emp_data_his values emp_rec;

使用referencing子句

在觸發器中也可以使用referencing子句來更改預設的謂詞名稱,比如可以将new子句更改為emp_new,将old子句更改為emp_old這樣的别名。

在指定了别名後,就可以在觸發體中使用:old_name和:new_name來代替:OLD和:NEW謂詞。

create or replace trigger t_vsal_ref

before update on emp

referencing OLD as emp_old NEW as emp_new for each row when(emp_new.sal>emp_old.sal)

if updating ('sal') then

v_sal:=:emp_new.sal - :emp_old.sal;

delete from emp_history where empno=:emp_old.sal;

insert into emp_history values(:emp_old.empno,:emp_old.ename,:emp_old.job,:emp_old.mgr,:emp_old.hiredate,:emp_old.sal,:emp_old.comm,:emp_old.deptno);

update emp_history set sal=v_sal where empno=:emp_new.empno;

使用when子句

when子句是在觸發器被觸發後,用來控制是否執行觸發體代碼的一個控制條件,在when子句中可以使用不帶冒号的new和old謂詞通路記錄的值,可以使用複合條件表達式組織多條記錄。

create or replace trigger t_emp_comm

before update on emp for each row when(new.comm>old.comm)

if updating ('comm') then

v_comm:=:NEW.comm - :OLD.comm;

insert into emp_history values(:OLD.empno,:OLD.ename,:OLD.mgr,:OLD.hiredate,:OLD.sal,:OLD.comm,:OLD.deptno);

update emp_history set comm=v_comm where empno=:NEW.empno;

使用條件謂語

條件謂語主要用來确定觸發器的DML語句的類型。

1.INSERTING

2.UPDATING

3.DELETING

使用UPDATING謂詞判斷特定字段的更新

create or replace trigger t_comm_sal

before update on emp for each row

when updating('comm') then

if :NEW.comm<:OLD.comm then

raise_application_error(-20001,'新的comm值不能小于舊的comm值');

when updating('sal') then

if :NEW.sal<:OLD.sal then

raise_application_error(-20001,'新的sal值不能小于舊的sal值');

控制觸發順序

使用FOLLOWS子句

create table trigger_data

trigger_id int,

trigger_name varchar2(100)

);

create or replace trigger one_trigger

before insert  on trigger_data for each row

:NEW.trigger_id:=:NEW.trigger_id+1;

dbms_output.put_line('觸發了one_trigger');

create or replace trigger two_trigger

before insert on trigger_data for each row follows one_trigger  --讓該觸發器在one_trigger後面觸發

dbms_output.put_line('觸發了two_trigger');

if :NEW.trigger_id > 1

:NEW.trigger_id:=:NEW.trigger_id+2;

實際上應用follows子句後,在兩個觸發器之間建立了依賴。使得two_trigger依賴于one_trigger,可以通過以下sql語句查詢

sql>select referenced_name,referenced_type,dependency_type from user_dependencies where name='TWO_TRIGGER' and referenced_type='TRIGGER';

觸發器限制

在編寫觸發器時,需要注意不能對觸發其所應用的基表中讀取或修改資料。

1.通常 行級别的觸發器不能讀寫觸發器所作用的基表,這個限制僅應用在行級觸發器上,但是語句級的觸發器可以自由地讀寫觸發器基表

2.如果在觸發器中使用自治事務,并在觸發體中送出事務,則可以查詢基表的内容,但是不能對基表進行任何的修改操作。

使用自治事務

1.如果在觸發器中抛出一個異常,将導緻整個事務復原

2.如果在觸發體中使用了DML操作,比如像日志表中插入日志記錄,那麼這些DML操作也屬于主事務的一部分,是以觸發體中任何意外操作也會導緻整個事務復原

3.在觸發體中不能使用commit或rollback語句,因為這會影響到主事務的執行

before update on emp for each row when(NEW.comm>OLD.comm)

v_comm number;

pragma autonomous_transaction;

if updating ('comm') then

v_comm := :NEW.comm - :OLD.comm;

delete from emp_history where empno=:OLD.comm;

insert into emp_history values(:OLD.empno,:OLD.ename,:OLD.job,:OLD.mgr,:OLD.hiredate,:OLD.sal,:OLD.comm,:OLD.deptno);

commit;

exception 

when others then

rollback;

替代觸發器

替代觸發器是觸發器類型中的另外一種,這種觸發器隻能定義在視圖上,當要對一個不能進行修改的視圖進行資料修改的時候,或者要修改視圖中的某個嵌套表的列時,可以使用替代觸發器。

替代觸發器,又稱為instead of 觸發器,之是以取這個名字,是因為觸發器将替代原來的資料操作語句的執行,更改為使用在觸發器中定義的語句來執行資料操作。

在學習視圖時曾經了解到,一些簡單的單表視圖,可以直接對其應,insert,update或delete語句進行更新,但是對于一些複雜的視圖,比如當視圖符合以下任何一種時,不能進行DML操作:

1.在定義視圖的查詢語句中使用了集合操作符,比如UNION,UNION ALL,INTERSECT,MINUS等

2.在視圖中使用了分組函數,比如MIN,MAX,SUM,AVG,COUNT等

3.使用了GROUP BY,CONNECT BY或START WITH等子句

4.具有DISTINCT關鍵字

5.使用了多表連接配接查詢

如果要對這種視圖進行修改,可以通過在視圖上編寫一個替代觸發器來完成正确的工作,這樣就允許對它進行修改了。

當使用者進行DML進行視圖操作時候,通過替代觸發器,将這些DML語句對視圖的更改替換成對基表的DML操作。注意事項:

1.替代觸發器隻能用于視圖

2.當建立替代觸發器時,不能指定before和after選項

3.當對視圖建立替代觸發器時,必須指定for each row方法

建立視圖

create or replace view scott.emp_dept(empno,ename,job,mgr,hiredate,sal,comm,deptno,dname,loc)

as

select emp.empno,emp.ename,emp.job,emp.mgr,emp.hiredate,emp.sal,emp.comm,emp.deptno,dept.dname,dept.loc from dept,emp where ((dept.deptno=emp.deptno));

create or replace trigger t_dept_emp

instead of insert on emp_dept

referencing new as n for each row

v_counter int;

select count(*) into v_counter from dept where deptno=:n.deptno;

if v_counter=0

insert into dept values(:n.deptno,:n.dname,:n.loc);

select count(*) into v_counter from emp where empno=:n.empno;

insert into emp(empno,ename,job,mgr,hiredate,sal,comm,deptno) values (:n.empno,:n.ename,:n.job,:n.mgr,:n.hiredate,:n.sal,:n.comm,:n.deptno);

替代觸發器必須使用for each row,表明對視圖的操作是一個行級的觸發器。

UPDATE與DELETE替代觸發器

create or replace trigger t_dept_emp_update

instead of update on emp_dept referencing NEW as n OLD as o

for each row

select count(*) into v_counter from dept where deptno=:o.deptno;

if v_counter>0

update dept set dname=:n.dname,loc=:n.loc where deptno=:o.deptno;

update emp set ename=:n.ename,job=:n.job,mgr=:n.mgr,hiredate=:n.hiredate,sal=:n.sal,comm=:n.comm,deptno=:n.deptno where empno=:o.empno;

DELETE觸發器

create or replace trigger t_dept_emp_delete

instead of update on emp_dept

referencing OLD AS o

delete from emp where empno=:o.empno;

delete from dept where deptno=:o.deptno;

替代觸發器完整示例

create or replace trigger t_emp_dept

instead of update or insert or delete on emp_dept

referencing NEW as n OLD as o

update dept set dname:=n.dname,loc=:n.loc where deptno=:o.deptno;

when deleting then

delete from dept where deptno=:o.deptno; 

insert into emp(empno,ename,job,mgr,hiredate,sal,comm,deptno) values (:n.empno,:n.ename,:n.job,:n.mgr,:n.hiredate,:n.sal,:n.comm,:n.deptno);

delete from emp empno=:o.empno;

嵌套表替代觸發器

如果在視圖的表列中使用了嵌套表,在要對視圖進行更新時,必須使用替代觸發器

僅在定義的視圖中包含的嵌套表列中才能使用替代觸發器,隻有使用THE()或TABLE()子句來修改視圖所包括的嵌套表上的列時,觸發器才會觸發。當視圖上的DML語句被執行時,觸發器不會被觸發.

建立用于嵌套表的對象類型

create or replace type emp_obj as object(

job   varchar2(10),

mgr   number(4),

hiredate DATE,

sal   number(7,2),

comm  number(7,2),

嵌套表類型

create or replace type emp_tab_type as table of emp_obj;

嵌套表視圖,MULTISET必須與cast一起使用

create or replace view dept_emp_view as 

select deptno,dname,loc,cast(MULTISET(select * from emp where deptno=dept.deptno) as emp_tab_type) emplst from dept;

不能直接在嵌套表視圖執行DML

建立嵌套表替代觸發器

create or replace dept_emp_innerview

instead of insert

on nested table emplst of dept_emp_view

insert into emp(deptno,empno,ename,job,mgr,hiredate,sal,comm) values(:PARENT.deptno,:NEW.empno,:NEW.ename,:NEW.job,:NEW.mgr,:NEW.hiredate,:NEW.sal,:NEW.comm);

代碼中使用了PARENT謂詞擷取嵌套表父行的deptno部門編号。

insert into table(select emplst from dept_emp_view where deptno=10) values (8003,'四爺','皇上',NULL,SYSDATE,5000,500,10);

嵌套表替代觸發器與普通的替代觸發器的建立方式基本相同,但是有如下兩個基本的差別

1.嵌套表使用"on nested table" 嵌套表列 of 嵌套表視圖 這種定義方式

2.parent 謂詞在嵌套表替代觸發器中具有值,指向包含嵌套表的視圖的父項記錄。

系統時間觸發器

DML觸發器和替代觸發器都是在DML事件上觸發的,相反,系統觸發器是在DDL事件和資料庫伺服器事件時觸發的,DDL包含create,alert或drop等語句,使得資料庫管理人員可以監控對資料庫的更改。

示例:如果要知道在scott下,建立表時的各類資訊,可以通過一個DDL觸發器,通過監控對CREATE語句的應用來實作。

在scott使用者下建立一個儲存DDL建立資訊的表

create table created_log

obj_owner varchar2(30),

obj_name  varchar2(30),

obj_type varchar2(20),

obj_user varchar2(30),

created_date DATE

create or replace trigger t_created_log

after create on scott.schema

insert into scott.created_log(obj_owner,obj_name,obj_type,obj_user,create_date) values(sys.dictionary_obj_owner,sys.dictionary_obj_name,sys.dictionary_obj_type,sys.login_user,SYSDATE);

觸發器可以在DATABASE或SCHEMA級别進行定義,這兩個關鍵詞用來确定系統觸發器的級别。

startup 和 shutdown觸發器隻能在database級别上建立,在方案級别上建立沒有意義,是以不會被觸發。

建立兩個觸發器,都用來監控使用者的LOGON事件,一個在方案級别觸發,一個在資料庫級别觸發。

create table log_db_table

username varchar2(20),

logon_time DATE,

logoff_time DATE,

address varchar2(20)

create table log_user_table

username varchar2(20),

logon_time DATE,

以DBA登入建立DATABASE級别的LOGON事件觸發器

create or replace trigger t_db_logon

after logon on database

insert into log_db_table(username,logon_time,address) values(ora_login_user,SYSDATE,ora_client_ip_address);

以scott登入,建立SCHEMA級别的事件觸發器

create or replace trigger t_user_logon

after logon on schema

insert into log_user_table(username,logon_time,address) values(ora_login_user,SYSDATE,ora_client_ip_address);

database級的記錄所有與資料庫連接配接相關的記錄。

觸發器屬性清單

oracle在DBMS_STANDARD包中提供了一些功能性的函數,以便在開發系統級别的觸發器時可以提供一些系統級别的資訊。

示例:startup和shutdown觸發器

create table event_table(

sys_event varchar2(30),

event_time DATE

create or replace trigger t_startup

after startup on database

insert into event_table values(ora_sysevent,SYSDATE);

before shutdown on database

屬性函數使用示例

在屬性函數清單中,ora_is_drop_column和ora_is_alter_column是兩個非常有用的屬性函數,在很多場合,可能希望一些表的字段不能被修改或移除,這樣在多人開發時可以防止開發人員的意外操作而出現意外,此時可以考慮建立alter或drop系統觸發器時,使用這兩個屬性函數來避免使用者進行非法的删除。

create or replace trigger preserve_app_cols

after alter on schema

cursor curs_get_columns(cp_owner varchar2,cp_table varchar2)

is

select column_name from all_tab_columns where owner=cp_owner and table_name=cp_table;

if ora_dict_obj_type ='TABLE'

for v_column_rec in curs_get_columns(

ora_dict_obj_owner,

ora_dict_obj_name

loop

if ora_is_alter_column(v_column_rec,column_name)

if v_column_rec.column_name='EMPNO' then

raise_application_error(-20003,'不能對empno字段進行修改');

end loop;

定義SERVERERROR觸發器

servererror事件可以用來跟蹤資料庫中發生的錯誤,錯誤代碼可以通過server_error屬性函數在觸發器内部得到,可以通過該函數确定堆棧中的錯誤代碼,可以使用DBMS_UTILITY.FORMAT_ERROR_STACK擷取錯誤資訊。

使用after servererror時必須要了解如下的錯誤是否會被觸發

ORA-00600:oracle内部錯誤

ORA-01034:oracle不可用

ORA-01403: 沒有找到資料

ORA-01422:提取操作傳回大于請求的行數

ORA-01423:在一個提取操作中檢測到額外的行

ORA-04030:在配置設定位元組時記憶體不夠。

after servererror觸發器在觸發器内部産生異常時也不會觸發,這樣會導緻死循環。

使用after servererror觸發器記錄錯誤日志

create table servererror_log(

error_time date,

username varchar2(30),

instance number,

db_name varchar2(50),

error_stack varchar2(2000)

create or replace trigger t_logerrors

after servererror on database

insert into servererror_log values(sysdate,login_user,instance_num,database_name,DBMS_UTILITY.format_error_stack);

觸發器的事務與限制

1.startup和shutdown觸發器不可以有任何條件,不能使用when子句

2.servererror觸發器可以用errno測試來檢查具體的錯誤

3.logon和logoff觸發器可以用userid或username測試來檢查使用者的辨別符或使用者名

4.DDL觸發器可以使用when子句檢查正被修改的對象的類型和名稱,并且可以檢查使用者辨別符或使用者名。

觸發器的管理

查詢emp表上定義了哪些觸發器

select trigger_name,trigger_type,table_name,triggering_event, status from user_triggers

where table_name='EMP';

如果想要建立一個一開就是被禁用的觸發器

create or replace trigger t_temp_testing

after insert on emp

disable

NULL;

觸發器名稱與權限的管理

觸發器的命名具有自己的名稱空間,所謂的名稱空間是指在這個範圍内用于對象名稱的合法辨別符集,所有這個名稱空間内的對象的命名必須唯一。觸發器的名稱空間與子程式,包和表的名稱空間不同。子程式,包和表具有相同的名稱空間,是以在一個方案内,如果子程式,包或表任何一個具有相同名稱,都會導緻不合法的命名。而觸發器存在于單獨的名稱空間,是以可以與表和過程具有相同的名稱,都會導緻不合法的命名。而觸發器存在于單獨的名稱空間,是以可以與表和過程具有相同的名稱,隻需要確定在一個方案下面所有的觸發器名稱不同,而不需要擔心與表,子過程和包重名。

觸發器是一個存儲在資料字典中的方案對象,除了觸發器本身要具有一定的通路權限之外,次觸發器的所有者必須對觸發器所引用的對象具有必要的對象特權,而且這些權限必須被直接賦予,而不能通過角色進行給予。

      本文轉自潘闊 51CTO部落格,原文連結:http://blog.51cto.com/pankuo/1630256,如需轉載請自行聯系原作者