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,从而违反了两个表间的关联完整性。