DML触发器的要点
DML触发器是定义在表上的触发器,由DML事件引发。编写DML触发器的要素是:
* 确定触发的表,即在其上定义触发器的表。
* 确定触发的事件,DML触发器的触发事件有INSERT、UPDATE和DELETE三种,说明见下。
* 确定触发时间。触发的时间有BEFORE和AFTER两种,分别表示触发动作发生在DML语句执行之前和语句执行之后。
* 确定触发级别,有语句级触发器和行级触发器两种。语句级触发器表示SQL语句只触发一次触发器,行级触发器表示SQL语句影响的每一行都要触发一次。
由于在同一个表上可以定义多个DML触发器,因此触发器本身和引发触发器的SQL语句在执行的顺序上有先后的关系。它们的顺序是:
* 如果存在语句级BEFORE触发器,则先执行一次语句级BEFORE触发器。
* 在SQL语句的执行过程中,如果存在行级BEFORE触发器,则SQL语句在对每一行操作之前,都要先执行一次行级BEFORE触发器,然后才对行进行操作。如果存在行级AFTER触发器,则SQL语句在对每一行操作之后,都要再执行一次行级AFTER触发器。
* 如果存在语句级AFTER触发器,则在SQL语句执行完毕后,要最后执行一次语句级AFTER触发器。
DML触发器还有一些具体的问题,说明如下:
* 如果有多个触发器被定义成为相同时间、相同事件触发,且最后定义的触发器是有效的,则最后定义的触发器被触发,其他触发器不执行。
* 一个触发器可由多个不同的DML操作触发。在触发器中,可用INSERTING、DELETING、UPDATING谓词来区别不同的DML操作。这些谓词可以在IF分支条件语句中作为判断条件来使用。
* 在行级触发器中,用:new 和:old(称为伪记录)来访问数据变更前后的值。但要注意,INSERT语句插入一条新记录,所以没有:old记录,而DELETE语句删除掉一条已经存在的记录,所以没有:new记录。UPDATE语句既有:old记录,也有:new记录,分别代表修改前后的记录。引用具体的某一列的值的方法是: :old.字段名或:new.字段名。触发器体内禁止使用COMMIT、ROLLBACK、SAVEPOINT语句,也禁止直接或间接地调用含有上述语句的存储过程。
定义一个触发器时要考虑上述多种情况,并根据具体的需要来决定触发器的种类。
DML触发器的创建
创建DML触发器需要CREATE TRIGGER系统权限。创建DML触发器的语法如下:
CREATE [OR REPLACE] TRIGGER 触发器名
{BEFORE|AFTER|INSTEAD OF} 触发事件1 [OR 触发事件2...]
ON 表名
WHEN 触发条件
[FOR EACH ROW]
DECLARE
声明部分
BEGIN
主体部分
END;
其中:
OR REPLACE:表示如果存在同名触发器,则覆盖原有同名触发器。
BEFORE、AFTER和INSTEAD OF:说明触发器的类型。
WHEN 触发条件:表示当该条件满足时,触发器才能执行。
触发事件:指INSERT、DELETE或UPDATE事件,事件可以并行出现,中间用OR连接。
对于UPDATE事件,还可以用以下形式表示对某些列的修改会引起触发器的动作:
UPDATE OF 列名1,列名2...
ON 表名:表示为哪一个表创建触发器。
FOR EACH ROW:表示触发器为行级触发器,省略则为语句级触发器。
触发器的创建者或具有DROP ANY TIRGGER系统权限的人才能删除触发器。删除触发器的语法如下:
DROP TIRGGER 触发器名
可以通过命令设置触发器的可用状态,使其暂时关闭或重新打开,即当触发器暂时不用时,可以将其置成无效状态,在使用时重新打开。该命令语法如下:
ALTER TRIGGER 触发器名 {DISABLE|ENABLE}
其中,DISABLE表示使触发器失效,ENABLE表示使触发器生效。
同存储过程类似,触发器可以用SHOW ERRORS 检查编译错误。
行级触发器的应用
在行级触发器中,SQL语句影响的每一行都会触发一次触发器,所以行级触发器往往用在对表的每一行的操作进行控制的场合。若在触发器定义中出现FOR EACH ROW子句,则为行级触发器。
【训练1】 创建包含插入、删除、修改多种触发事件的触发器DML_LOG,对EMP表的操作进行记录。用INSERTING、DELETING、UPDATING谓词来区别不同的DML操作。
在创建触发器之前,需要先创建事件记录表LOGS,该表用来对操作进行记录。该表的字段含义解释如下:
LOG_ID:操作记录的编号,数值型,它是该表的主键,由序列自动生成。
LOG_TABLE:进行操作的表名,字符型,非空,该表设计成可以由多个触发器共享使用。比如我们可以为dept表创建类似的触发器,同样将操作记录到该表。
LOG_DML:操作的动作,即INSERT、DELETE或UPDATE三种之一。
LOG_KEY_ID:操作时表的主键值,数值型。之所以记录表的主键,是因为主键是表的记录的惟一标识,可以识别是对哪一条记录进行了操作。对于emp表,主键是empno。
LOG_DATE:操作的日期,日期型,取当前的系统时间。
LOG_USER:操作者,字符型,取当时的操作者账户名。比如登录SCOTT账户进行操作,在该字段中,记录账户名为SCOTT。
步骤1:在SQL*Plus中登录STUDENT账户,创建如下的记录表LOGS:
CREATETABLElogs(
LOG_ID NUMBER(10)PRIMARYKEY,
LOG_TABLE VARCHAR2(10)NOTNULL,
LOG_DML VARCHAR2(10),
LOG_KEY_ID NUMBER(10),
LOG_DATEDATE,
LOG_USER VARCHAR2(15)
);
执行结果:表已创建。
步骤2:创建一个LOGS表的主键序列LOGS_ID_SEQ:
CREATESEQUENCElogs_id_squ INCREMENTBY1
STARTWITH1 MAXVALUE 9999999 NOCYCLE NOCACHE;
执行结果:序列已创建。
步骤3:创建和编译以下触发器:CREATEORREPLACETRIGGERDML_LOG
BEFORE--触发时间为操作前
DELETEORINSERTORUPDATE-- 由三种事件触发
ONemp
FOREACH ROW-- 行级触发器
BEGIN
IF INSERTINGTHEN
INSERTINTOlogsVALUES(logs_id_squ.NEXTVAL,'EMP','INSERT',:new.empno,SYSDATE,USER);
ELSIF DELETINGTHEN
INSERTINTOlogsVALUES(logs_id_squ.NEXTVAL,'EMP','DELETE',:old.empno,SYSDATE,USER);
ELSE
INSERTINTOlogsVALUES(logs_id_squ.NEXTVAL,'EMP','UPDATE',:new.empno,SYSDATE,USER);
ENDIF;
END;
执行结果:
触发器已创建
步骤4:在EMP表中插入记录:
INSERTINTOemp(empno,ename,job,sal)VALUES(8001,'MARY','CLERK',1000);
COMMIT;
执行结果:
已创建1行。
提交完成。
步骤5:检查LOGS表中记录的信息:
SELECT*FROMLOGS;
执行结果为:LOG_ID LOG_TABLE LOG_DML LOG_KEY_ID LOG_DATE LOG_USER
----------------- ----------------- ------------------ ----------------------- ---------------- -------------------
1 EMPINSERT8001 29-3月 -04 STUDENT
已选择 1 行。
说明:本例中在emp表上创建了一个由INSERT或DELETE或UPDATE事件触发的行级触发器,触发器的名称是LOG_EMP。对于不同的操作,记录的内容不同。本例中只插入了一条记录,如果用一条不带WHERE条件的UPDATE语句来修改所有雇员的工资,则将逐行触发触发器。
INSERT、DELETE和UPDATE都能引发触发器动作,在分支语句中使用INSERTING、DELETING和UPDATING来区别是由哪种操作引发的触发器动作。
在本例的插入动作中,LOG_ID字段由序列LOG_ID_SQU自动填充为1;LOGS表LOG_KEY_ID字段记录的是新插入记录的主键8001;LOD_DML字段记录的是插入动作INSERT;LOG_TABLE字段记录当前表名EMP;LOG_DATE字段记录插入的时间04年3月1日;LOG_USER字段记录插入者STUDENT。
【训练3】 创建触发器CHECK_SAL,当对职务为CLERK的雇员的工资修改超出500至2000的范围时,进行限制。
步骤1:创建和编译以下触发器:
Sql代码
CREATEORREPLACETRIGGERCHECK_SAL
BEFORE
UPDATE
ONemp
FOREACH ROW
BEGIN
IF :new.job='CLERK'AND(:new.sal<500OR:new.sal>2000)THEN
RAISE_APPLICATION_ERROR(-20001,'工资修改超出范围,操作取消!');
ENDIF;
END;CREATE OR REPLACE TRIGGER CHECK_SAL
BEFORE
UPDATE
ON emp
FOR EACH ROW
BEGIN
IF :new.job='CLERK' AND (:new.sal<500 OR :new.sal>2000) THEN
RAISE_APPLICATION_ERROR(-20001, '工资修改超出范围,操作取消!');
END IF;
END;
执行结果:
Sql代码
触发器已创建。触发器已创建。
步骤2:在EMP表中插入记录:
Sql代码
UPDATEempSETsal=800WHEREempno=7876;
UPDATEempSETsal=450WHEREempno=7876;
COMMIT;UPDATE emp SET sal=800 WHERE empno=7876;
UPDATE emp SET sal=450 WHERE empno=7876;
COMMIT;
执行结果:
Sql代码
UPDATEempSETsal=450WHEREempno=7876
*
ERROR 位于第 1 行:
ORA-20001: 工资修改超出范围,操作取消!
ORA-06512: 在"STUDENT.CHECK_SAL", line 3
ORA-04088: 触发器'STUDENT.CHECK_SAL'执行过程中出错提交完成。UPDATE emp SET sal=450 WHERE empno=7876
*
ERROR 位于第 1 行:
ORA-20001: 工资修改超出范围,操作取消!
ORA-06512: 在"STUDENT.CHECK_SAL", line 3
ORA-04088: 触发器 'STUDENT.CHECK_SAL' 执行过程中出错提交完成。
步骤3:检查工资的修改结果:
Sql代码
SELECTempno,ename,job,salFROMempWHEREempno=7876;SELECT empno,ename,job,sal FROM emp WHERE empno=7876;
执行结果为:
Sql代码
EMPNO ENAME JOB SAL
----------------- ------------- ------------- ------------------------
7876 ADAMS CLERK 800EMPNO ENAME JOB SAL
------------------ ------------- ------------- ------------------------
7876 ADAMS CLERK 800
说明:在触发器中,当IF语句的条件满足时,即对职务为CLERK的雇员工资的修改超出指定范围时,用RAISE_APPLICATION_ERROR语句来定义一个临时定义的异常,并立即引发异常。由于触发器是BEFORE类型,因此触发器先执行,触发器因异常而终止,SQL语句的执行就会取消。
通过步骤2的执行信息可以看到,第一条语句修改编号为7876的雇员ADAMS的工资为800,成功执行。第二条语句修改雇员ADAMS的工资为450,发生异常,执行失败。这样就阻止了不符合条件的工资的修改。通过步骤3的查询可以看到,雇员ADAMS最后的工资是800,即发生异常之前的修改结果。
语句级触发器的应用
同行级触发器不同,语句级触发器的每个操作语句不管操作的行数是多少,只触发一次触发器,所以语句级触发器适合于对整个表的操作权限等进行控制。在触发器定义中若省略FOR EACH ROW子句,则为语句级触发器。
【训练1】 创建一个语句级触发器CHECK_TIME,限定对表EMP的修改时间为周一至周五的早8点至晚5点。
步骤1:创建和编译以下触发器:
Sql代码
CREATEORREPLACETRIGGERCHECK_TIME
BEFORE
UPDATEORINSERTORDELETE
ONEMP
BEGIN
IF (TO_CHAR(SYSDATE,'DY')IN('SAT','SUN'))
ORTO_CHAR(SYSDATE,'HH24')
ORTO_CHAR(SYSDATE,'HH24')>='17'THEN
RAISE_APPLICATION_ERROR(-20500,'非法时间修改表错误!');
ENDIF;
END;CREATE OR REPLACE TRIGGER CHECK_TIME
BEFORE
UPDATE OR INSERT OR DELETE
ON EMP
BEGIN
IF (TO_CHAR(SYSDATE,'DY') IN ('SAT','SUN'))
OR TO_CHAR(SYSDATE,'HH24')< '08'
OR TO_CHAR(SYSDATE,'HH24')>='17' THEN
RAISE_APPLICATION_ERROR(-20500,'非法时间修改表错误!');
END IF;
END;
执行结果:
Sql代码
触发器已创建。触发器已创建。
步骤2:当前时间为18点50分,在EMP表中插入记录:
Sql代码
UPDATEEMPSETSAL=3000WHEREEMPNO=7369;UPDATE EMP SET SAL=3000 WHERE EMPNO=7369;
显示结果为:
Sql代码
UPDATEEMPSETSAL=3000WHEREEMPNO=7369
*
ERROR 位于第 1 行:
ORA-20500: 非法时间修改表错误!
ORA-06512: 在"STUDENT.CHECK_TIME", line 5
ORA-04088: 触发器'STUDENT.CHECK_TIME'执行过程中出错UPDATE EMP SET SAL=3000 WHERE EMPNO=7369
*
ERROR 位于第 1 行:
ORA-20500: 非法时间修改表错误!
ORA-06512: 在"STUDENT.CHECK_TIME", line 5
ORA-04088: 触发器 'STUDENT.CHECK_TIME' 执行过程中出错
说明:通过引发异常限制对数据库进行的插入、删除和修改操作的时间。SYSDATE用来获取系统当前时间,并按不同的格式字符串进行转换。“DY”表示获取英文表示的星期简写,“HH24”表示获取24小时制时间的小时。
当在18点50分修改表中的数据时,由于时间在8点至17点(晚5点)之外,所以产生“非法时间修改表错误”的用户自定义错误,修改操作终止。
如果觉得《oracle dml触发器 获取当前执行sql语句 Oracle DML触发器》对你有帮助,请点赞、收藏,并留下你的观点哦!