oracle数据库笔记1PL/SQL基础7触发器

触发器是特定事件出现的时候,自动执行的代码块。
类似于存储过程,但是用户不能直接调用他们

功能:
1、 允许/限制对表的修改
2、 自动生成派生列,比如自增字段
3、 强制数据一致性
4、 提供审计和日志记录
5、 防止无效的事务处理
6、 启用复杂的业务逻辑



create trigger biufer_empployees_department_id before
  insert or update of department_id 
  on employees
  referencing old as old_value new as new_value
  for each row
    when(new_value.department_id!=80)
begin
    :new_value.commision_pct:=0;
end;

触发器的组成部分:
1、 触发器名称
2、 触发语句
3、 触发器限制
4、 触发操作


1、 触发器名称
create trigger biufer_employees_department_id
命名习惯:biufer(before insert update for each row)
employees 表名
department_id 列名


2、 触发语句
表或视图上的dml语句
ddl语句
数据库关闭或启动,startup shutdown 等等

before insert or
    update of department_id
  on employees
 referencing old as old_value
     new as new_value
 for each row


1)规定了对employees表进行insert的时候  触发器会激活
2)对employees表的department_id列进行update的时候  触发器会激活

3、 触发器限制

when (new_value.department_id<>80 )
其中的new_value是代表更新之后的值

限制不是必须的

4、 触发操作

是触发器的主体
begin
 :new_value.commission_pct :=0;
end;

主体很简单,就是将更新后的commission_pct列置为0




触发时机

在dml触发器中,根据触发时机不同,可以分为before和after,但是在触发过程中其顺序不同。

执行顺序为:
1.before触发器 

2.约束检查 

3.更新表 

4.after触发器

Before触发器是在约束之前执行的
通常用于:
1.设置或修改更新或插入的列值
2.检查复杂的安全规则,如限制时间
3.增强商业应用规则
4.可以通过触发器的逻辑潜在的引发一个异常来拒绝触发语句,则相对有效,因为是在约束前执行


After触发器最后执行
一般用于:
1.用户信息的审计

2.导出数据的生成
3.远程数据的复制

触发器类型:
1、 语句触发器
2、 行触发器
3、 instead of 触发器
4、 系统条件触发器
5、 用户事件触发器

1)语句触发器

是在表上或者某些情况下的视图上执行的特定语句或者语句组上的触发器

能够与insert、update、delete或者组合上进行关联

但是无论使用什么样的组合,各个语句触发器都只会针对指定语句激活一次

比如,无论update多少行,也只会调用一次update语句触发器

ey:需要对在表上进行dml操作的用户进行安全检查,看是否具有合适的特权
create table foo(a number);
create trigger biud_foo
  before insert or update or delete
   on foo
begin
  if user not in (‘donny’) then
   raise_application_error(-20001, ‘you don’t have access to modify this table.’);
  end if;
end;

ey:对修改表的时间、人物进行日志记录
1、 建立试验表
create table employees_copy as select *from emp;
2、 建立日志表
create table employees_log(
  who varchar2(30),
  when date);

3、 在employees_copy表上建立语句触发器,在触发器中填充employees_log 表
create or replace trigger biud_employee_copy
  before insert or update or delete
  on employees_copy
begin
  insert into employees_log(who,when)
  values( user, sysdate);
end;

4、 测试
update employees_copy set salary= salary*1.1;

select *from employess_log;

5、 确定是哪个语句起作用?
即是insert/update/delete中的哪一个触发了触发器?
可以在触发器中使用inserting / updating / deleting 条件谓词,作判断:

begin
  if inserting then
   -----
  elsif updating then
   -----
  elsif deleting then
   ------
  end if;
end;


if updating(‘col1’) or updating(‘col2’) then
  ------
end if;


a.修改日志表
alter table employees_log add (action varchar2(20));


b.修改触发器,以便记录语句类型
create or replace trigger biud_employee_copy
  before insert or update or delete
  on employees_copy
 declare
  l_action employees_log.action%type;
 begin
  if inserting then
     l_action:=’insert’;
  elsif updating then
     l_action:=’update’;
  elsif deleting then
     l_action:=’delete’;
  else
    raise_application_error(-20001,’you should never ever get this error.’);
 
   end if;

  insert into employees_log(who,action,when)
  values( user, l_action,sysdate);
end;

c.测试
update employees_copy set salary=50000 where employee_id = 12345;

select *from employees_log
;

2)行触发器

是指为受到影响的各个行激活的触发器,定义与语句触发器类似,有以下两个例外:
1、 定义语句中包含for each row子句
2、 在for each row触发器中,用户可以引用受到影响的行值。

create trigger biufer_empployees_department_id before
  insert or update of department_id 
  on employees
  referencing old as old_value new as new_value
  for each row
    when(new_value.department_id!=80)
begin
    :new_value.commision_pct:=0;
end;

referencing 子句:
执行dml语句之前的值的默认名称是 :old ,之后的值是 :new
insert 操作只有:new
delete 操作只有 :old
update 操作两者都有

referencing子句只是将new 和old重命名为new_value和old_value,目的是避免混淆。
比如操作一个名为new的表时。

ey:为主健生成自增序列号

drop table foo;
create table foo(id number, data varchar2(20));
create sequence foo_seq;

create or replace trigger bifer_foo_id_pk
 before insert on foo
 for each row
begin
 select foo_seq.nextval into :new.id from dual;
end;

语句与行级触发器的区别?
	行级触发器							语句级触发器
被触发语句影响的每一行所触发					不管影响多少行,触发语句只执行一次
若触发事件对行没有产生更改影响,则不触发			即使触发事件不影响任何一行,触发器也触发
都可指定before,after						都可指定before,after

触发器执行体可以读取行数据					触发器执行体与行数据无关
只受当前触发事件影响						只受当前触发事件影响

After行触发器锁定数据行						不锁定数据行

同一表上触发器的优先级别
同一表上可以定义多个触发器,最多可以创建12种类型的触发器,但是其触发是有固定顺序的,其先后顺序为:

1.before语句级触发器

2.before行级触发器

3.after行级触发器

4.after语句级触发器


3)instead of 触发器更新视图

create or replace view v_emp_dept as
 select empno, ename , hiredate, sal,deptno, dname from emp,dept
  where emp.deptno=dept.deptno;

尝试往v_emp_dept表插入数据时只能通过替代触发器来完成

create or replace trigger tr_v

  instead of insert on v_emp_dept

  for each row

begin
  
insert into dept values(:new.deptno,:new.dname);
  
insert into emp (empno,ename,hiredate,sal) values(:new.empno,:new.ename,:new.hiredate,:new.sal);

end;


替代触发器的使用经验

替代触发器可用来操纵对视图的插入,修改和删除。
Before和after无法用于替代触发器

视图上的check约束对替代触发器无效,因此需要在触发器语句内加强相关约束
Dml触发器是在dml操作外运行的,而替代触发器则代替触发它的dml命令运行。本质上,替代触发器属于行级。

4)系统事件触发器
系统事件:数据库启动、关闭,服务器错误

create trigger ad_startup
 after startup
  on database
begin
 -- do some stuff
end;

5)用户事件触发器

用户事件:用户登陆、注销,create / alter / drop / analyze / audit / grant / revoke / rename / truncate / logoff
ey:记录删除对象
a.日志表
create table droped_objects(
object_name varchar2(30),
object_type varchar2(30),
dropped_on date);

b.触发器

create or replace trigger log_drop_trigger
 before drop on donny.schema
begin
 insert into droped_objects
  values(
      ora_dict_obj_name,  -- 与触发器相关的函数
    ora_dict_obj_type,
    sysdate);
end;


c.测试


create table drop_me(a number);
create view drop_me_view as select *from drop_me;
drop view drop_me_view;
drop table drop_me;


select *from droped_objects



禁用和启用触发器
alter trigger <trigger_name> disable;
alter trigger <trigger_name> enable;

事务处理:
在触发器中,不能使用commit / rollback
因为ddl语句具有隐式的commit,所以也不允许使用

触发器的限制
触发器的使用存在一些限制条件,需要引起高度重视!

1.create trigger语句文本字符长度不能超过32k,
如果触发器的逻辑编码超过60行,则最好将主要代码保存到存储过程。
使用时之需要从触发器中调用存储过程即可。

2.不要创建递归式触发器,否则,系统会因为触发器与表之间的循环操作而导致内存耗尽,系统崩溃。

3.触发器中不能使用数据库事务控制语句commit,rollback和savepoint

4.由触发器所调用的过程或者函数也不能使用数据库事务控制语句。

5.触发器中不能声明long,long raw类型变量,也不能在触发表的long 或long raw列上使用:new和:old.


6.触发器体内可以读取Lob类型的列值,但不能通过:new修改lob列的数据。

7.when条件子句只适用于行级触发器,一旦指定了when ,触发器只执行满足when子句条件的行

问题:

写一个ORACLE 触发器,主要是解决一个系统的管理员帐号经常被培训的人修改的问题.
功能是在Update 用户表时如果是修改的管理员,
则将管理员的密码改回到初始状态(假定初始密码是oldpassword)
,用户表是user(id ,password,job_title)


CREATE OR REPLACE TRIGGER before_update_password 
  before update of password 
  on new_users
  for each row 
  when (new.jobtitle='admin')
  declare
    
err exception;
 
begin

    dbms_output.put_line('trigger active');

    if :old.jobtitle='admin'  then

      raise err;
    
end if;
 
exception 
    
when err then

      raise_application_error(-20009,'pls do not change admin password');
    
when others then

        dbms_output.put_line(sqlerrm);

 end;



触发器与变异表

变异表:当前正在被DML语句更新的表,对于触发器而言,变异表就是在其上定义该触发器的那张表。

问题:将每一门课程的选修人数限制在2人,在students表上定义一个before insert or update行级触发器实现

students(Id,major)
create or replace trigger limitmajors
 
  before insert or update
 of major
  on students
 
  for each row

  declare
    
 v_maxstudents constant number:=2;

     v_currentstudents number;

  begin

     select count(*) into v_currentstudents
     from students where major=:new.major;
    
 if(v_currentstudents+1>v_maxstudents) then
 
        raise_application_error(-20001,‘too many students major in ‘||:new.major);
    
 end if;
 
  end;


创建测试环境
create table students(id number primary key,major varchar2(20));

insert into students values(1,'math');
insert into students values(2,'math');
insert into students values(3,'math');

用insert  ….select 子句测试
create table students_copy as select * from students where 1=2;

insert into  students_copy values(3,‘math’);
insert into students
select * from students_copy;

用update测试
insert into students values(3,'english');
update students set major='math' where id=3;

修改思路如下:
将上面的触发器修改成2个触发器,行级与语句级触发器

1.在行级触发器中,可以记录 :new.major 的值,但不查询students表
2.查询在语句级触发器中实现

需要 保存一些全局性变量以在两个触发器间传递变量,可以用包来实现:

Create or replace package studentdata as
  Type t_majors is table OF students.major%type
  Index by BINARY_INTEGER;
  Type t_IDs is table OF students.ID%type
  Index by BINARY_INTEGER;
  V_studentmajors  t_majors;
  V_studentids     t_ids;
  V_numentries   binary_integer:=0;
End studentdata;

行级触发器
Create or replace trigger rlimitmajors
  Before insert or update of major on students
  For each row
Begin
  Studentdata.v_numentries:=studentdata.v_numentries+1;
  Studentdata.v_studentmajors(studentdata.v_numentries):=:new.major;  
  Studentdata.v_studentids(studentdata.v_numentries):=:new.id;
End rlimitmajors;

语句触发器
Create or replace trigger slimitmajors
  after insert or update of major on students
  declare
     v_maxstudents constant number:=2;
     v_currentstudents number;
     v_studentid students.id%type;
     v_major students.major%type;
  begin
     for v_loopindex in 1..studentdata.v_numentries loop
       v_studentid :=studentdata.v_studentids(v_loopindex);
       v_major:= studentdata.v_studentmajors(v_loopindex);
       
select count(*) into v_currentstudents
       from students where major= v_major;
       If V_currentstudents > V_maxstudents then
          RAISE_APPLICATION_ERROR(-20000,’too many students in major ’||v.major);
       End if;
       End loop;
       Studentdata.v_numentries:=0;
  End slimitmajors;

说明
1.确保变量成为全局变量的唯一方法就是将它存放在某个包中。
2.注意用after语句级触发器中进行处理

  

原文地址:https://www.cnblogs.com/wust221/p/3073611.html