3.9.4 视图上的触发器

视图是建立在一个或多个表之上的虚表。视图中的数据来自对基表的查询。对于简单视图,可以对其进行DELETE、UPDATE和INSERT等DML操作。如果是复杂视图,则不允许对其执行DML操作。

在视图上也可以建立触发器。建立视图触发器的语法为:


CREATE[OR REPLACE]TRIGGER 触发器

INSTEAD OF

DELETE|INSERT|UPDATE[OF列名]

ON 视图

[FOR EACH ROW]

BEGIN

PL/SQL语句;

END;


与表上的触发器相比,视图上的触发器既不是前触发(BEFORE)的,也不是后触发(AFTER)的,而是“INSTEAD OF”类型的。“INSTEAD OF”的意思是当在视图上进行DML操作时,不去执行指定的DML语句,而是执行触发器的代码,这样对视图进行的DML操作根本就不会执行,取代它执行的是触发器的代码。

视图上的触发器都是行触发器,因此,在创建触发器时,“FOR EACH ROW”子句可以省略。对视图进行DML操作时,受影响的每一行数据都会激发触发器执行一次。在触发器中也可以使用关联行NEW和OLD。

如果是简单视图,可以对其进行DML操作。在简单视图上建立触发器的目的一般是为了进行安全校验,例如,我们可以在简单视图上建立触发器,禁止某些DML操作,或者将一些DML操作转化为其他的操作。

现在,我们首先用下面的SQL语句在表emp上建立一个简单视图view_1。


CREATE OR REPLACE VIEW view_1

AS

SELECT ename, empno, sal, deptno FROM emp


接下来,我们在视图view_1上建立一个触发器trg_emp_view,如果对这个视图进行DELETE或者UPDATE操作时,将激发这个触发器。


CREATE OR REPLACE TRIGGER trg_emp_view

INSTEAD OF DELETE OR UPDATE

ON view_1

FOR EACH ROW

BEGIN

if deleting then

if:old.deptno=30 then

raise_application_error(-20001,'部门30的员工不能删除');

END if;

END if;

if updating then

if:new.sal<=:old.sal then

raise_application_error(-20002,'该员工的工资没有增加');

END if;

END if;

END;


这个触发器的目的是当删除部门30的员工时抛出异常,或者减少员工的工资时抛出异常。如果执行下面的DML语句:


SQL>DELETE FROM view_1 WHERE deptno=30;

执行结果为:

DELETE FROM view_1 WHERE deptno=30

*

ERROR位于第1行:

ORA-20001:部门30的员工不能删除

ORA-06512:在"SCOTT.TRG_EMP_VIEW",line 4

ORA-04088:触发器'SCOTT.TRG_EMP_VIEW'执行过程中出错


如果执行下面的DML语句:


SQL>DELETE FROM view_1 WHERE deptno<>30;


执行结果为:


已删除8行


执行结果表明最后一条DML语句似乎已经顺利执行,但是查询视图view_1的结果表明这条DML语句并没有执行。这是因为当对视图执行规定的DML命令时,将激发触发器,DML操作不会真正执行,取代它执行的是触发器的代码。在上面的触发器中,仅仅对删除部门30的操作进行判断,而对其他部门的删除操作没有做任何进一步的处理,所以会得到数据已被删除的假象。这一点与表上的触发器是完全不同的,使用时要特别小心。

如果对上述视图进行UPDATE操作,将得到同样的结果。如果减少员工的工资,将产生一个异常。如果增加员工工资,表面上看UPDATE操作已经执行成功,实际上工资并没有修改。

如果是复杂视图,则不能在视图上进行DML操作。复杂视图是建立在多个表之上的视图,或者是使用了分组函数的视图。

考虑用以下SQL语句创建的视图:


CREATE OR REPLACE VIEW view_2

AS SELECT emp.ename, emp.empno, emp.sal, emp.deptno, dept.dname FROM emp, dept

WHERE emp.deptno=dept.deptno


如果要在这个视图上执行DML操作,将会发生错误。例如:


SQL>INSERT INTO view_2 VALUES('SMITH',7799,2000,10,'SALES');

INSERT INTO view_2 VALUES('SMITH',7799,2000,10,'ACCOUNTING')


*

ERROR位于第1行:

ORA-01776:无法通过连接视图修改多个基表


如果在这个视图上创建一个触发器,那么当在这个视图上进行DML操作时,将激发触发器的执行。如果在触发器中指定了其他操作,DML语句就会以其他方式执行,这样就把用户的DML语句转化为其他的SQL语句了。

例如,对于视图view_2上的INSERT操作,可以将其转化为另一条INSERT语句,用于向emp表中插入一行。当然,如果在写入数据时指定的部门号在表dept中不存在,触发器的执行就会发生错误。对于DELETE操作,可以将其转化为另一条DELETE语句,仅从EMP表中删除相关部门的员工。创建这个触发器的语句为:


CREATE OR REPLACE TRIGGER trg_1

INSTEAD OF INSERT OR DELETE

ON view_2

FOR EACH ROW

BEGIN

if INSERTING then

INSERT INTO emp(ename, empno, sal, deptno)

VALUES(:new.ename,:new.empno,:new.sal,:new.deptno);

END if;

if DELETING then

DELETE FROM emp WHERE empno=:old.empno;

END if;

END;


创建触发器后,视图上的DML语句就可以顺利执行了。当然,DML语句本身并没有执行,而是转化为另外的DML语句执行了。例如,再执行刚才的INSERT语句:


SQL>INSERT INTO view_2 VALUES('SMITH',7799,2000,10,'SALES');

已创建1行。


在视图上进行的DML操作还得遵守基表上的完整性约束。例如,如果在视图view_2上执行INSERT操作,如果指定的部门号在dept表中不存在,或者指定的员工号在表emp中已经存在,都将违反完整性约束。例如:


SQL>INSERT INTO view_2 VALUES('PANDA',8800,2000,90,'DEVELOP')

INSERT INTO view_2 VALUES('PANDA',8800,2000,90,'DEVELOP')

*

ERROR 位于第1行:

ORA-02291:违反完整约束条件(SCOTT.FK_DEPTNO)-未找到父项关键字

ORA-06512:在"SCOTT.TRG_1",line 3

ORA-04088:触发器'SCOTT.TRG_1'执行过程中出错


发生错误的原因是在表dept中不存在部门90,从而违反了两个表间的关联完整性。