第十五章 触发器 |
15.1分类: |
DML触发器 -----------基于表的(insert、alter、update) |
替代触发器 -----------基于VIEW的 |
系统触发器 -----------基于系统的 |
好处:自动调用、记录日志、保证数据安全、用数据库触发器可以保证数据的一致性和完整性。 |
语法: |
CREATE [ OR REPLACE ] TRIGGER trigger_name |
{BEFORE | AFTER } |
{ INSERT | DELETE | UPDATE [ OF column [, column …]]} |
[ OR { INSERT | DELETE | UPDATE [ OF column [, column …]]}...] |
ON [ schema .]table_name | [ schema .]view_name |
[REFERENCING {OLD [ AS ] old | NEW [ AS ] new| PARENT as parent}] |
[ FOR EACH ROW ] |
[ WHEN condition] |
PL/SQL_BLOCK | CALL procedure_name; |
|
BEFORE 和 AFTER 指出触发器的触发时序分别为前触发和后触发方式,前触发是在执行触发事件之前触发当前所创建的触发器,后触发是在执行触发事件之后触发当前所创建的触发器。 |
FOR EACH ROW选项说明触发器为行触发器。行触发器和语句触发器的区别表现在:行触发器要求当一个DML语句操走影响数据库中的多行数据时,对于其中的每个数据行,只要它们符合触发约束条件,均激活一次触发器;而语句触发器将整个语句操作作为触发事件,当它符合约束条件时,激活一次触发器。当省略 FOR EACH ROW 选项时,BEFORE 和 AFTER 触发器为语句触发器,而 INSTEAD OF 触发器则只能为行触发器。 |
REFERENCING 子句说明相关名称,在行触发器的PL/SQL块和 WHEN 子句中可以使用相关名称参照当前的新、旧列值,默认的相关名称分别为OLD和NEW。触发器的PL/SQL块中应用相关名称时,必须在它们之前加冒号(:),但在 WHEN 子句中则不能加冒号。 |
WHEN 子句说明触发约束条件。Condition 为一个逻辑表达时,其中必须包含相关名称,而不能包含查询语句,也不能调用PL/SQL 函数。 WHEN 子句指定的触发约束条件只能用在BEFORE 和 AFTER 行触发器中,不能用在 INSTEAD OF 行触发器和其它类型的触发器中。 |
15.2、 触发器触发次序 |
1. 执行 BEFORE语句级触发器; |
2. 对与受语句影响的每一行: |
l 执行 BEFORE行级触发器 |
l 执行 DML语句 |
l 执行 AFTER 行级触发器 |
3. 执行 AFTER 语句级触发器 |
15.3、语句触发器 |
after 语句触发器 |
Before 语句触发器 |
例如:禁止工作人员在休息日改变雇员信息 |
create or replace trigger tr_src_emp |
before insert or update or delete |
on emp |
begin |
if to_char(sysdate, 'DY' , 'nls_date_language=AMERICAN' ) in ( 'SAT' , 'SUN' ) then |
raise_application_error(-20001, 'can‟t modify user information in weekend' ); |
end if; |
end ; |
/ |
|
使用条件谓语 ---------inserting、updating、deleting |
create or replace trigger tr_src_emp |
before insert or update or delete |
on emp |
begin |
if to_char(sysdate, 'DY' ) in ( '星期六' , '星期天' ) then |
case |
when inserting then |
raise_application_error(-20001, 'fail to insert' ); |
when updating then |
raise_application_error(-20001, 'fail to update' ); |
when deleting then |
raise_application_error(-20001, 'fail to delete' ); |
end case ; |
end if; |
end ; |
/ |
15.4、行触发器 |
执行 DML 操作时,每作用一行就触发一次触发器。 |
Bofre 行触发器 |
例如:确保员工工资不能低于原有工资 |
Create or replace trigger tr_emp_sal |
before update of sal |
on emp |
for each row |
begin |
if :new.sal<:old.sal then |
raise_application_error(-20010, 'sal should not be less' ); |
end if; |
end ; |
/ |
after 行触发器 |
例如:统计员工工资变化 |
Create table audit_emp_change( |
Name varchar2(10), |
Oldsal number(6,2), |
Newsal number(6,2), |
Time date ); |
Create or replace trigger tr_sal_sal |
after update of sal |
on emp |
for each row |
declare |
v_temp int ; |
begin |
select count (*) into v_temp from audit_emp_change where name =:old.ename; if v_temp=0 then |
insert into audit_emp_change values (:old.ename,:old.sal,:new.sal,sysdate); else |
update audit_emp_change set oldsal=:old.sal,newsal=:new.sal, time =sysdate where name =:old.ename; |
end if; |
end ; |
/ |
限制行触发器 |
Create or replace trigger tr_sal_sal |
after update of sal |
on emp |
for each row when (old.job=‟SALESMAN‟) |
declare |
v_temp int ; |
begin |
select count (*) into v_temp from audit_emp_change where name =:old.ename; |
if v_temp=0 then |
insert into audit_emp_change values (:old.ename,:old.sal,:new.sal,sysdate); |
else |
update audit_emp_change set oldsal=:old.sal,newsal=:new.sal, time =sysdate where name =:old.ename; |
end if; |
end ; |
/ |
注意: |
例如:如果要基于 EMP 表建立触发器。那么该触发器的执行代码不能包含对 EMP 表的查询操作编写 DML 触发器的时,触发器代码不能从触发器所对应的基表中读取数据。 |
Create or replace trigger tr_emp_sal |
Before update of sal |
on emp |
For each row |
declare |
Maxsal number(6,2); |
Begin |
If :new.sal>maxsal then Select max (sal) into maxsal from emp; |
Raise_application_error(-21000,‟error‟); |
End if; |
End ; |
/ |
创建的时候不会报错。但是一旦执行就报错了 |
update emp set sal=sal*1.1 where deptno=30 |
DML触发器的限制 |
l CREATE TRIGGER 语句文本的字符长度不能超过32KB; |
l 触发器体内的 SELECT 语句只能为 SELECT … INTO …结构,或者为定义游标所使用的 SELECT 语句。 |
l 触发器中不能使用数据库事务控制语句 COMMIT ; ROLLBACK , SVAEPOINT 语句; |
触发器不能对多表有效 |
l 由触发器所调用的过程或函数也不能使用数据库事务控制语句; |
l 触发器中不能使用LONG, LONG RAW 类型; |
l 触发器内可以参照LOB 类型列的列值,但不能通过 :NEW 修改LOB列中的数据; |
|
:NEW 修饰符访问操作完成后列的值 |
:OLD 修饰符访问操作完成前列的值 |
特性 |
INSERT |
UPDATE |
DELETE |
OLD |
NULL |
实际值 |
实际值 |
NEW |
实际值 |
实际值 |
NULL |
|
15.4 替代触发器 |
语法: |
CREATE [ OR REPLACE ] TRIGGER trigger_name |
INSTEAD OF |
{ INSERT | DELETE | UPDATE [ OF column [, column …]]} |
[ OR { INSERT | DELETE | UPDATE [ OF column [, column …]]}...] |
ON [ schema .] view_name --只能定义在视图上 |
[REFERENCING {OLD [ AS ] old | NEW [ AS ] new| PARENT as parent}] |
[ FOR EACH ROW ] --因为INSTEAD OF触发器只能在行级上触发,所以没有必要指定 |
[ WHEN condition] --指定触发器的约束 |
PL/SQL_block | CALL procedure_name; |
例2:创建复杂视图,针对 INSERT 操作创建 INSTEAD OF 触发器,向复杂视图插入数据。 |
l 创建视图: |
CREATE OR REPLACE FORCE VIEW "HR" . "V_REG_COU" ( "R_ID" , "R_NAME" , "C_ID" , "C_NAME" ) |
AS |
SELECT r.region_id, |
r.region_name, |
c.country_id, |
c.country_name |
FROM regions r, |
countries c |
WHERE r.region_id = c.region_id; |
|
l 创建触发器: |
CREATE OR REPLACE TRIGGER "HR" . "TR_I_O_REG_COU" |
INSTEAD OF INSERT |
ON v_reg_cou |
FOR EACH ROW |
DECLARE |
v_count NUMBER; |
BEGIN |
SELECT COUNT (*) INTO v_count FROM regions WHERE region_id = :new.r_id; |
IF v_count = 0 THEN |
INSERT INTO regions |
(region_id, region_name |
) VALUES |
(:new.r_id, :new.r_name |
); |
END IF; |
SELECT COUNT (*) INTO v_count FROM countries WHERE country_id = :new.c_id; |
IF v_count = 0 THEN |
INSERT |
INTO countries |
( |
country_id, |
country_name, |
region_id |
) |
VALUES |
( |
:new.c_id, |
:new.c_name, |
:new.r_id |
); |
END IF; |
END ; |
|
创建 INSTEAD OF 触发器需要注意以下几点: |
l 只能被创建在视图上,并且该视图没有指定 WITH CHECK OPTION 选项。 |
l 不能指定BEFORE 或 AFTER 选项。 |
l FOR EACH ROW子可是可选的,即 INSTEAD OF 触发器只能在行级上触发、或只能是行级触发器,没有必要指定。 |
l 没有必要在针对一个表的视图上创建 INSTEAD OF 触发器,只要创建DML触发器就可以了。 |
|
15.5、系统事件触发器 |
语法: |
CREATE OR REPLACE TRIGGER [sachema.]trigger_name |
{BEFORE |
{BEFORE| AFTER } |
{ddl_event_list |
{ddl_event_list | database_event_list} |
ON { DATABASE | [ schema .] SCHEMA } |
[ WHEN condition] |
PL/SQL_block | CALL procedure_name; |
其中: ddl_event_list:一个或多个DDL 事件,事件间用 OR 分开; |
database_event_list:一个或多个数据库事件,事件间用 OR 分开; |
下面给出系统触发器的种类和事件出现的时机(前或后): |
事件 |
允许的时机 |
说明 |
STARTUP |
AFTER |
启动数据库实例之后触发 |
SHUTDOWN |
BEFORE |
关闭数据库实例之前触发(非正常关闭不触发) |
SERVERERROR |
AFTER |
数据库服务器发生错误之后触发 |
LOGON |
AFTER |
成功登录连接到数据库后触发 |
LOGOFF |
BEFORE |
开始断开数据库连接之前触发 |
CREATE |
BEFORE, AFTER |
在执行 CREATE 语句创建数据库对象之前、之后触发 |
DROP |
BEFORE, AFTER |
在执行 DROP 语句删除数据库对象之前、之后触发 |
ALTER |
BEFORE, AFTER |
在执行 ALTER 语句更新数据库对象之前、之后触发 |
DDL |
BEFORE, AFTER |
在执行大多数DDL语句之前、之后触发 |
GRANT |
BEFORE, AFTER |
执行 GRANT 语句授予权限之前、之后触发 |
REVOKE |
BEFORE, AFTER |
执行 REVOKE 语句收权限之前、之后触犯发 |
RENAME |
BEFORE, AFTER |
执行RENAME语句更改数据库对象名称之前、之后触犯发 |
AUDIT / NOAUDIT |
BEFORE, AFTER |
执行AUDIT或NOAUDIT进行审计或停止审计之前、之后触发 |
例1: |
/* create or replace trigger tr_emp1_1 |
--before update on emp1 |
after update on emp1 |
for each row |
declare |
-- local variables here |
begin |
if :new.sal<:old.sal then |
Raise_application_error(-20000, ':new.sal>:old.sal' ); |
end if; |
end tr;*/ |
/* create or replace trigger tr_emp1_2 |
after delete on emp1 |
--before delete on emp1 |
for each row |
begin |
Raise_application_error(-20001, '不能删' ); |
end ;*/ |
create or replace trigger tr_emp1_3 |
after delete or update or insert on emp1 |
for each ROW |
begin |
if deleting then |
if :new.sal>0 then |
Raise_application_error(-20000, 'deleting' ); |
end IF; |
/*elsif updating then |
if :new.sal<:old.sal then |
Raise_application_error(-20001, 'updating' ); |
end if; |
else |
if :new.sal<:old.sal then |
Raise_application_error(-20002, 'inserting' ); |
end if;*/ |
end if; |
end ; |
例2: |
--替代触发器 |
create or replace trigger t_emp1 |
instead OF INSERT OR DELETE OR UPDATE on v_emp1 |
declare |
-- local variables here |
BEGIN |
IF inserting THEN |
INSERT INTO dept1 |
(deptno, dname, loc) |
VALUES |
(:new.deptno, :new.dname,:new.loc); |
INSERT INTO emp1 |
(empno, ename, job, mgr, hiredate, sal, comm, deptno) |
VALUES |
(:new.empno, :new.ename, :new.job, :new.mgr, :new.hiredate, :new.sal, :new.comm, :new.deptno); |
ELSIF deleting THEN |
DELETE FROM emp1 |
WHERE empno=:OLD.EMPNO; |
DELETE FROM dept1 |
WHERE deptno=:old.DEPTNO; |
ELSE |
UPDATE emp1 |
SET empno=:new.empno, |
ename=:new.ename, |
job = :new.job, |
mgr=:new.mgr, |
hiredate = :new.hiredate, |
sal= :new.sal, |
comm= :new.comm, |
deptno= :new.deptno |
WHERE :old.empno=:new.empno; |
UPDATE dept1 |
SET deptno = :new.deptno, |
dname = :new.dname, |
loc = :new.loc |
WHERE :old.deptno=:new.deptno; |
END IF; |
end t_emp1; |
--系统触发器 |
CREATE OR REPLACE TRIGGER t_emp1_1 |
AFTER drop /* OR CREATE OR ALTER */ ON c##rain. schema |
DECLARE |
BEGIN |
INSERT INTO demo VALUES (SYSDATE, USER ,ora_dict_obj_name, |
ora_client_ip_address, |
ora_database_name); |
END ; |
调试 |
DELETE FROM v_emp1 WHERE empno=7369; |
SELECT * FROM v_emp1 |
SELECT * FROM dept1; |
SELECT * FROM emp1; |
INSERT INTO v_emp1(empno,deptno) VALUES (221,11); |
INSERT INTO v_emp1(empno) VALUES (2); |
UPDATE v_emp1 SET ename= '123' WHERE empno=7893; |
SELECT * FROM emp2; |
DROP TABLE emp2; |
CREATE TABLE a(s NUMBER); |
DROP TABLE a; |
SELECT * FROM demo; |
SELECT * FROM a; |
ALTER TABLE demo ADD table_name VARCHAR2(20); |
ALTER TABLE demo ADD user_ip NUMBER; |
ALTER TABLE demo ADD database_name VARCHAR2(31); |
ALTER TABLE demo ADD table_name VARCHAR2(20); |
SELECT table_name FROM User_Tables; |
资料: |
http://www.cnblogs.com/huyong/archive/2011/04/27/2030466.html |