PL/SQL --> INSTEAD OF 觸發器
--==============================
-- PL/SQL --> INSTEAD OF 觸發器
--==============================
INSTEAD OF 觸發器常用於管理編寫不可更新的檢視,INSTEAD-OF觸發器必須是行級的。
可以用INSTEAD OF觸發器來解釋INSERT、UPDATE和DELETE語句,並用備用的程式程式碼替換那些指令。
一、不可更新檢視
基於下列情形建立的檢視,不可直接對其進行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]
[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觸發器的應用
在工作中,有時候需要將兩個或多個表中的欄位進行同步的問題。即假定有表A和B,表A中的欄位COLa和表B中的欄位COLb需要時時保持同
步,當表A中COLa被更新時,需要將更新的內容同步到表B的COLb中,反之,當表B的COLb被更新時,需要將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_a,tb_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觸發器不能指定BEFORE和AFTER選項
INSTEAD OF觸發器,必須指定FOR EACH ROW
當建立的檢視被重新定義之後,基於檢視上建立的觸發器將需要重新定義
六、更多參考
有關SQL請參考
有關PL/SQL請參考