3.9.3 行触发器
如果在某个表上创建了一个触发器,在对这个表进行DML操作时,每影响一行数据,该触发器都将被激发执行一次,那么这个触发器就是行触发器。首先看一个非常简单的触发器的例子:
CREATE OR REPLACE TRIGGER emp_trg
AFTER DELETE
ON emp
FOR EACH ROW
BEGIN
dbms_output.put_line('1行已经被删除');
END;
这个触发器是后触发的。如果对表emp进行DELETE操作,将激发这个触发器的执行。在创建触发器时使用了FOR EACH ROW子句,因此这个触发器是行触发器,例如,如果执行了以下的DELETE语句,执行结果为:
SQL>DELETE FROM emp WHERE deptno=20;
1行已经被删除
1行已经被删除
1行已经被删除
1行已经被删除
1行已经被删除
已删除5行。
执行结果中的最后一行是SQL*Plus的统计信息,表明有5行受到DELETE语句的影响。从触发器执行的输出信息可以看出,触发器确实执行了5次。也就是说,触发器执行的次数等于受DML操作影响的数据行数。
如果DML操作没有影响到任何一行数据,那么触发器将不执行。例如,如果要根据一个不存在的deptno列的值从emp表中删除行,结果如下:
SQL>DELETE FROM emp WHERE deptno=100;
已删除0行。
可见,如果DML语句没有影响到任何行,触发器将不会执行。
如果我们更关心DML语句对每行数据的访问情况,而不是DML操作本身的信息,那么在表上创建行触发器更合适。
在创建行触发器时,我们可以指定一些条件,这样只有当特定的数据受到DML语句影响时,触发器才被激发执行。创建触发器时,可以在FOR EACH ROW子句之后使用WHEN子句指定条件。
例如,重新考虑上面的简单的行触发器。如果只对部门30进行监视,只有当从表emp中删除deptno列值为30的行时,才激发触发器。这个触发器可以这样创建:
CREATE OR REPLACE TRIGGER emp_trg
AFTER DELETE
ON emp
FOR EACH ROW WHEN(OLD.deptno=30)
BEGIN
dbms_output.put_line('1行已经被删除');
END;
如果执行两次DELETE操作,分别删除deptno为30和20的行,执行结果为:
SQL>DELETE FROM emp WHERE deptno=30;
1行已经被删除
……
已删除6行。
SQL>DELETE FROM emp WHERE deptno=20;
已删除5行。
可见,当从表emp中删除部门30的员工时,触发器被激发,而删除其他部门的员工时,没有激发触发器。
在行触发器中,同样可以使用条件谓词INSERT、UPDATING和DELETING,以判断当前所进行的DML操作。
行触发器通常用于对用户的DML操作进行合法性检查,使得用户修改数据的操作必须按照一定的规则进行。
为了能够比较修改前和修改后的数据,在触发器的可执行代码中,可以使用两个关联行—NEW和OLD。它们分别表示触发器被激发时,当前行的原数据和新数据。
NEW表示修改后的行,通过NEW可以引用新行中的各个列的值,如NEW.ename。OLD表示修改前的行,通过OLD可以引用原来的各个列的值,如OLD.ename。
对于UPDATE命令,OLD表示原来的行,NEW表示修改后的行。对于INSERT命令,OLD没有意义,NEW表示新写入的一行。对于DELETE命令,NEW没有意义,OLD表示被删除的一行。
在触发器的可执行代码中,如果要通过OLD和NEW引用某个列的值,要在前面加上“:”,在其他地方,则不用使用“:”。
现在,我们要创建一个比较复杂的触发器,这个触发器对表emp上的DML操作进行监视。这个触发器需要满足以下要求:
1)在DML操作执行之前进行合法性检查。
2)如果要从表emp中删除一行数据,不能删除部门30的员工。如果是其他部门的员工,则删除这行数据,并把这一行数据在另一个表中进行备份。
3)如果要写入一行数据,要保证这个员工的工资高于1000。
4)如果修改表emp的sal列,应保证新工资比原工资要高,并把员工的工资变化情况记录在另一个表中。
为此,我们需创建两个表,一个是del_action,用来保存从表emp中删除的行,它的结构与表emp相同。可以使用下面的语句创建一个空表del_action:
CREATE TABLE del_action
AS
SELECT*FROM emp
WHERE deptno=0;
第二个表是update_action,用来记录员工工资的变化情况。这个表中的各个字段定义如表3-9所示。
现在我们可以根据上面的要求创建触发器了。创建这个触发器的语句为:
CREATE OR REPLACE TRIGGER emp_dml_trg
BEFORE
INSERT OR DELETE OR UPDATE OF sal
ON emp
FOR EACH ROW
BEGIN
if INSERTING then
if:new.sal is null or:new.sal<1000 then
dbms_output.put_line('新员工的工资不得低于1000元');
END if;
END if;
if UPDATING then
if:new.sal<=:old.sal then
dbms_output.put_line('员工'||:old.empno||'的工资没有增加');
else
INSERT INTO update_action VALUES(
:old.empno,:old.ename,:old.sal,:new.sal, to_char(sysdate,'yyyy-mm-dd hh:mi:ss'),user);
END if;
END if;
if DELETING then
if:old.deptno=30 then
dbms_output.put_line('部门'||:old.deptno||'的员工不得删除');
else
INSERT INTO del_action VALUES(
:old.empno,:old.ename,:old.job,:old.mgr,:old.hiredate,:old.sal,:old.comm,:old.deptno);
END if;
END if;
END;
在触发器中指定的规则与表上的约束不同。约束是一种强制性的规则,如果DML操作违反了约束,那么这次DML操作是无效的,对数据将不做任何修改。如果DML操作违反了触发器的规则,触发器将按照既定的方法进行响应,但是它并不阻止DML语句的执行。
例如,如果用户执行下面的DELETE语句,从表emp中删除数据:
SQL>DELETE FROM emp WHERE deptno=30;
这条语句的执行结果为:
部门30的员工不得删除
1行已经被删除
部门30的员工不得删除
1行已经被删除
……
已删除6行。
一方面,触发器按照既定的方法进行响应,结果是正确的。另一方面,DELETE语句却顺利执行了。这是因为,触发器只能对用户的DML操作进行合法性检查,但并不能阻止DML操作的执行,它并不是一种强制性的规则。
如果触发器在检查到不合规定的DML操作时,抛出一个异常,那么这次DML操作对数据所做的修改是无效的,也就是说,触发器阻止了DML语句的执行。例如,把上面的触发器emp_dml_trg重新进行定义,则可以保证不合规定的DML操作不会影响任何数据。创建新的触发器的代码如下:
CREATE OR REPLACE TRIGGER emp_dml_trg
BEFORE
INSERT OR DELETE OR UPDATE OF sal
ON emp
FOR EACH ROW
BEGIN
if INSERTING then
if:new.sal is null or:new.sal<1000 then
raise_application_error(-2 0000,'新员工的工资不得低于1000元');
END if;
END if;
if UPDATING then
if:new.sal<=:old.sal then
raise_application_error(-20001,'员工'||:old.empno||'的工资没有增加');
else
INSERT INTO update_action VALUES(
:old.empno,:old.ename,:old.sal,:new.sal, to_char(sysdate,'yyyy-mm-dd hh:mi:ss'),user);
END if;
END if;
if DELETING then
if:old.deptno=30 then
raise_application_error(-20002,'部门'||:old.deptno||'的员工不得删除');
else
INSERT INTO del_action VALUES(
:old.empno,:old.ename,:old.job,:old.mgr,:old.hiredate,:old.sal,:old.comm,:old.deptno);
END if;
END if;
END;
这样,如果用户执行了不合规定的DML操作,触发器将抛出相应的异常,并终止触发器的执行,用户的DML操作不会对表中的数据造成任何改变。例如,如果用户试图修改员工的工资,使每个人的工资比原来少100,UPDATE语句的执行结果为:
SQL>UPDATE emp set sal=sal-100;
UPDATE emp set sal=sal-100
*
ERROR位于第1行:
ORA-20001:员工7369的工资没有增加
ORA-06512:在"SCOTT.EMP_DML_TRG",line 9
ORA-04088:触发器'SCOTT.EMP_DML_TRG'执行过程中出错