1. 程式人生 > >PL/SQL --> INSTEAD OF 觸發器

PL/SQL --> INSTEAD OF 觸發器

--==============================

-- PL/SQL --> INSTEAD OF 觸發器

--==============================

INSTEAD OF 觸發器常用於管理編寫不可更新的檢視,INSTEAD-OF觸發器必須是行級的。

可以用INSTEAD OF觸發器來解釋INSERTUPDATEDELETE語句,並用備用的程式程式碼替換那些指令。

一、不可更新檢視

基於下列情形建立的檢視,不可直接對其進行DML操作

使用了集合操作運算子(UNION,UNION ALL ,INTERSECT,MINUS)

使用了分組函式(MIN,MAX,SUM,AVG)

使用了GROUP BY ,CONNECT BY ,START WITH 子句

使用了DISTINCT 關鍵字

使用了連線查詢

對於基於上述情況建立的檢視,不能對其直接執行DML,但可以在該檢視上建立INSTEAD OF觸發器來間接執行DML

二、建立INSTEAD OF 觸發器的語法

CREATE [OR REPLACE] TRIGGER trigger_name

INSTEAD OF {dml_statement }

ON {object_name | database | schema}

FOR EACH ROW

[WHEN (logical_expression)]

[DECLARE]

declaration_statements;

BEGIN

execution_statements;

END [trigger_name];

/

三、建立檢視

--在下面建立的檢視中,由於使用了連線查詢,因此檢視將不可更新

CREATE OR REPLACE VIEW vw_dept_emp

AS

SELECT deptno,d.dname,e.empno,e.ename

FROM dept d

JOIN emp e

USING (deptno);

--從資料字典(user_updatable_columns)中查詢某一檢視哪些列是可更新或不可更新的

[email protected]

> col owner format a15

[email protected]> select * from user_updatable_columns where table_name='VW_DEPT_EMP';

OWNERTABLE_NAMECOLUMN_NAMEUPD INS DEL

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

SCOTTVW_DEPT_EMPDEPTNOYES YES YES

SCOTTVW_DEPT_EMPDNAMENONONO--可以看到列DNAME不能執行DML

SCOTTVW_DEPT_EMPEMPNOYES YES YES

SCOTTVW_DEPT_EMPENAMEYES YES YES

--嘗試更新檢視時,更新失敗

[email protected]> update vw_dept_emp set dname='Developement' where deptno=10;

update vw_dept_emp set dname='Developement' where deptno=10

*

ERROR at line 1:

ORA-01779: cannot modify a column which maps to a non key-preserved table

[email protected]> update vw_dept_emp set ename='Henry' where empno=7369;

1 row updated.

[email protected]> select empno,ename,job from emp where empno=7369;

EMPNO ENAMEJOB

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

7369 HenryCLERK

--建立一個基於UPDATE INSTEAD OF 觸發器

CREATE OR REPLACE TRIGGER tr_vw_dept_emp

INSTEAD OF UPDATE

ON vw_dept_emp

FOR EACH ROW

BEGIN

UPDATE dept

SET dname=:new.dname

WHERE deptno=:old.deptno;

END;

--更新檢視

[email protected]> update vw_dept_emp set dname='Developement' where deptno=20;

4 rows updated.

--驗證更新後的結果

[email protected]> select * from vw_dept_emp where rownum<2 and deptno=20;

DEPTNO DNAMEEMPNO ENAME

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

20 Developement7369 Henry

[email protected]> select * from dept where deptno=20;

DEPTNO DNAMELOC

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

20 DevelopementDALLAS

四、INSTEAD OF觸發器的應用

在工作中,有時候需要將兩個或多個表中的欄位進行同步的問題。即假定有表AB,表A中的欄位COLa和表B中的欄位COLb需要時時保持同

步,當表ACOLa被更新時,需要將更新的內容同步到表BCOLb中,反之,當表BCOLb被更新時,需要將COLb的內容更新到A表的COLa中。

對於這樣的問題,按照一般的想法是在表A和表B分別建立觸發器來使之保持同步,但實際上表A和表B上的觸發器將會被迭代觸發,即A表的

更新將觸發B表上的觸發器,而B表上的觸發器反過來又觸發A上的觸發器,最終的結果是導致變異表的產生。基於此,我們可以使用INSTEAD

OF 觸發器完成此項任務,下面給出全部過程。

--分別建立表tb_a,tb_b並插入記錄

[email protected]> create table tb_a(ID int,COLa varchar2(40));

[email protected]> create table tb_b(ID int,COLb varchar2(40));

[email protected]> insert into tb_a select 1,'Robinson' from dual;

[email protected]> insert into tb_b select 1,'Jackson' from dual;

[email protected]> commit;

--在表tb_a上建立觸發器

CREATE OR REPLACE TRIGGER tr_tb_a

BEFORE UPDATE ON tb_a

FOR EACH ROW

DECLARE

lv_newcolVARCHAR2(40);

lv_oldcolVARCHAR2(40);

BEGIN

lv_newcol := :new.COLa;

lv_oldcol := :old.COLa;

IF lv_newcol <> lv_oldcol THEN

UPDATE tb_b

SET COLb = :new.COLa

WHERE ID = :new.ID;

END IF;

DBMS_OUTPUT.PUT_LINE(lv_oldcol ||'=>'|| lv_newcol);

END;

--更新表tb_a時,表tb_b的欄位也被更新

[email protected]> update tb_a set COLa='Willson' where ID=1;

Robinson=>Willson

[email protected]> select * from tb_b;

ID COLB

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

1 Willson

--在表B上建立觸發器

CREATE OR REPLACE TRIGGER tr_tb_b

BEFORE UPDATE ON tb_b

FOR EACH ROW

DECLARE

lv_newcolVARCHAR2(40);

lv_oldcolVARCHAR2(40);

BEGIN

lv_newcol := :new.COLb;

lv_oldcol := :old.COLb;

IF lv_newcol <> lv_oldcol THEN

UPDATE tb_a

SET COLa = :new.COLb

WHERE ID = :new.ID;

END IF;

DBMS_OUTPUT.PUT_LINE(lv_oldcol ||'=>'|| lv_newcol);

END;

--更新表tb_b時,出現了表變異的提示,同樣更新表tb_a時也會出現類似的提示

[email protected]> update tb_b set COLb='Other'where ID=1;

update tb_b set COLb='Other'where ID=1

*

ERROR at line 1:

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

ORA-06512: at "SCOTT.TR_TB_A", line 8

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

ORA-06512: at "SCOTT.TR_TB_B", line 8

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

--禁用觸發器

[email protected]> alter trigger tr_tb_a disable;

[email protected]> alter trigger tr_tb_b disable;

--分別在表tb_atb_b上建立檢視

[email protected]> create view vw_tb_a as select * from tb_a;

[email protected]> create view vw_tb_b as select * from tb_b;

--基於檢視vw_tb_a建立instead of 觸發器

CREATE OR REPLACE TRIGGER tr_vw_tb_a

INSTEAD OF UPDATE ON vw_tb_a

FOR EACH ROW

DECLARE

lv_newcolVARCHAR2(40);

lv_oldcolVARCHAR2(40);

BEGIN

lv_newcol := :new.COLa;

lv_oldcol := :old.COLa;

IF lv_newcol <> lv_oldcol THEN

UPDATE tb_a

SET COLa = :new.COLa

WHERE ID = :new.ID;

UPDATE tb_b

SET COLb = :new.cola

WHERE ID=:new.ID;

END IF;

DBMS_OUTPUT.PUT_LINE(lv_oldcol ||'=>'|| lv_newcol);

END;

--基於檢視vw_tb_b建立instead of 觸發器

CREATE OR REPLACE TRIGGER tr_vw_tb_b

INSTEAD OF UPDATE ON vw_tb_b

FOR EACH ROW

DECLARE

lv_newcolVARCHAR2(40);

lv_oldcolVARCHAR2(40);

BEGIN

lv_newcol := :new.COLb;

lv_oldcol := :old.COLb;

IF lv_newcol <> lv_oldcol THEN

UPDATE tb_a

SET COLa = :new.COLb

WHERE ID = :new.ID;

UPDATE tb_b

SET COLb = :new.colb

WHERE ID=:new.ID;

END IF;

DBMS_OUTPUT.PUT_LINE(lv_oldcol ||'=>'|| lv_newcol);

END;

--對檢視進行更新,驗證成功

[email protected]> update vw_tb_a set COLa='Many' where ID = 1;

Willson=>Many

[email protected]> select * from tb_b;

ID COLB

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

1 Many

[email protected]> update vw_tb_b set COLb='Much' where ID = 1;

Many=>Much

[email protected]> select * from tb_a;

ID COLA

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

1 Much

五、總結

檢視建立時未指定WITH CHECK OPTION選項

INSTEAD OF觸發器只適用於檢視

基於檢視的INSTEAD OF觸發器不能指定BEFOREAFTER選項

INSTEAD OF觸發器,必須指定FOR EACH ROW

當建立的檢視被重新定義之後,基於檢視上建立的觸發器將需要重新定義

六、更多參考

有關SQL請參考

有關PL/SQL請參考