当前位置:   article > 正文

Oracle 触发器(tigger)4

Oracle 触发器(tigger)4

Oracle 触发器(tigger)

1 触发器类型

触发器在数据库里以独立的对象存储,它与存储过程和函数不同的是,存储过程与函数需要用户显示调用才执行,而触发器是由一个事件来启动运行。即触发器是当某个事件发生时自动地隐式运行。并且,触发器不能接收参数。所以运行触发器就叫触发或点火(firing)。

触发器常用来完成由数据库的完整性约束难以完成的复杂业务规则的约束,或用来监视对数据库的各种操作,实现审计的功能。

1.1 DML触发器

ORACLE可以在DML(insert、update、delete)语句进行,可以在DML操作前或操作后进行触发,并且可以对每个行或语句操作上进行触发。

1.1.1 单列触发:if 列名

create or replace trigger scott.tr_au_student_info
  after update of sno, name on scott.student_info
  for each row
begin
  if :new.sno <> :old.sno or :new.name <> :old.name then
    raise_application_error(-20001,
                            '禁止操作!修改 sno = ' || :new.sno || ', name = ' ||
                            :new.name);
  end if;
end;

--测试语句:

update scott.student_info t
   set t.name = '哈哈'
 where t.sno = 1;
 
 --测试结果:
 弹框 - 错误提醒
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

1.2 DDL 触发器

触发事件:create、alter、drop

-- **********************************************************************
-- 功能:非 DBA 管理员禁止操作, 如:wangyou
--      限定符合下列 类型 和 域账户 的人,才能操作 create、alter、drop
-- **********************************************************************
create or replace trigger scott.tr_dba_control
  before create or alter or drop on database
declare
  v_user_name   varchar2(50); -- 用户名
begin
  select sys_context('USERENV', 'OS_USER') into v_user_name from dual;
  if dbms_standard.dictionary_obj_type in
     ('TABLE', 'SYNONYMS', 'USER', 'TABLESPACE') and
     v_user_name not in ('wangyou') then
    raise_application_error(-20000,
                            v_user_name || '用户无 DDL-' || ora_sysevent ||
                            ' 权限,请联系数据架构设计处处理!');
  end if;
end;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

1.3 替代触发器

由于在ORACLE里,不能直接对由两个以上的表建立的视图进行操作。所以给出了替代触发器。它就是ORACLE 8专门为进行视图操作的一种处理方法。

  1. 只适用于视图(多个简单的基表相连),不能直接作用于表上(间接)

  2. 很少使用,不如 dml 触发器来得直观

  3. 必须包含 for each row 选项

create or replace trigger <触发器名称>   
    instead of insert or update or delete on <视图名>   
    for each row *-- 必填,且唯一* 
begin   
	pl/sql 语句; 
end;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

1.4 系统触发器

ORACLE 8i 提供了第三种类型的触发器叫系统触发器。它可以在ORACLE数据库系统的事件中进行触发,如ORACLE系统的启动与关闭等。

触发器组成:

​ 1.触发事件:引起触发器被触发的事件。 例如:DML语句(INSERT, UPDATE, DELETE语句对表或视图执行数据处理操作)、DDL语句(如CREATE、ALTER、DROP语句在数据库中创建、修改、删除模式对象)、数据库系统事件(如系统启动或退出、异常错误)、用户事件(如登录或退出数据库)。

​ 2.触发时间:即该TRIGGER 是在触发事件发生之前(BEFORE)还是之后(AFTER)触发,也就是触发事件和该TRIGGER 的操作顺序。

​ 3.触发操作:即该TRIGGER 被触发之后的目的和意图,正是触发器本身要做的事情。 例如:PL/SQL 块。

​ 4.触发对象:包括表、视图、模式、数据库。只有在这些对象上发生了符合触发条件的触发事件,才会执行触发操作。

​ 5.触发条件:由WHEN子句指定一个逻辑表达式。只有当该表达式的值为TRUE时,遇到触发事件才会自动执行触发器,使其执行触发操作。

​ 6.触发频率:说明触发器内定义的动作被执行的次数。即语句级(STATEMENT)触发器和行级(ROW)触发器。

7.语句级(STATEMENT)触发器:是指当某触发事件发生时,该触发器只执行一次;

8.行级(ROW)触发器:是指当某触发事件发生时,对受到该操作影响的每一行数据,触发器都单独执行一次。

编写触发器时,需要注意以下几点:

​ 1. 触发器不接受参数。

​ 2.一个表上最多可有12个触发器,但同一时间、同一事件、同一类型的触发器只能有一个。并各触发器之间不能有矛盾。

​ 3.在一个表上的触发器越多,对在该表上的DML操作的性能影响就越大。

​ 4.触发器最大为32KB。若确实需要,可以先建立过程,然后在触发器中用CALL语句进行调用。

​ 5. 在触发器的执行部分只能用DML语句(SELECT、INSERT、UPDATE、DELETE),不能使用DDL语句(CREATE、ALTER、DROP)

​ 6.触发器中不能包含事务控制语句(COMMIT,ROLLBACK,SAVEPOINT)。因为触发器是触发语句的一部分,触发语句被提交、回退时,触发器也被提交、回退了。

​ 7.在触发器主体中调用的任何过程、函数,都不能使用事务控制语句。

​ 8.在触发器主体中不能申明任何Long和blob变量。新值new和旧值old也不能是表中的任何long和blob列。

​ 9.不同类型的触发器(如DML触发器、INSTEAD OF触发器、系统触发器)的语法格式和作用有较大区别。

1.5Databse 触发器

触发事件:
startup:'数据库打开'时,相反的 = shutdown 
logon  :当用户连接到数据库并 '建立会话' 时,相反的 = logoff 
servererror:发生服务器错误时
  • 1
  • 2
  • 3
  • 4
--建表
create table scott.database_login_info (
   client_ip      varchar2(30),
   login_user     varchar2(30),
   database_name  varchar2(30),
   database_event varchar2(30),
   create_user    varchar2(50),
   create_data    date
);

--触发器
create or replace trigger scott.tr_al_database_login_info
  after logon on database
declare
  v_option_user varchar2(50) := sys_context('USERENV', 'OS_USER'); -- 电脑域账户
begin
  insert into scott.database_login_info
    (client_ip,
     login_user,
     database_name,
     database_event,
     create_user,
     create_data)
  values
    (dbms_standard.client_ip_address,
     dbms_standard.login_user,
     dbms_standard.database_name,
     dbms_standard.sysevent,
     v_option_user,
     sysdate);
end;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

s

2. 创建触发器

create [or replace] trigger 触发器名
   触发时间 {before | after}           -- view 中是 instead of
   触发事件 {insert | update | delete} -- dml、ddl、database
on 触发对象                            -- table、view、schema、database 
   触发频率 {for each row}            -- 行级触发器。默认:语句级触发器
   [follows 其它触发器名]              -- 多个触发器执行的 前后顺序
   [when 触发条件]
begin
   pl/sql 语句;
end;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

关键字说明:

  1. 触发器名:一般格式 tr_*

  2. 触发时间:在 ‘触发事件’ 发生之前(before)还是之后(after)

  3. 触发事件:根据不同的 ‘触发事件’,可以分为不同的 ‘类型’

  4. 触发对象:table、view、schema、database

  5. 触发频率:‘语句级触发器’(默认)指触发一次,‘行级触发器’ 每一行触发一次

  6. 触发条件:仅当 ‘触发条件’ 为 True 时,才执行 pl/sql 语句

    基础数据准备:

    create table scott.student_info (
      sno   number(10),
      name  varchar2(30),
      sex   varchar2(2)
    );
    insert into scott.student_info(sno, name, sex) values(1, '张三', '女');
    insert into scott.student_info(sno, name, sex) values(2, '李四', '男');
    insert into scott.student_info(sno, name, sex) values(3, '王五', '女');
    commit;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

2.1for each row

/*
功能:after insert or update or delete 时,执行语句
命名:tr_aiud_student_info
*/
create or replace trigger scott.tr_aiud_student_info
   after insert or update or delete on scott.student_info
   for each row
begin
   case
      when inserting then
         dbms_output.put_line('插入成功!');
      when updating then
         dbms_output.put_line('更新成功!');
      when deleting then
         dbms_output.put_line('删除成功!');
      else
         dbms_output.put_line('无操作!');
   end case;
end;
/

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

测试语句:

update scott.student_info t
   set t.sex = '1'
 where t.sno <= 3;
  • 1
  • 2
  • 3

PL/SQL 输出窗口:3 条记录,故共触发 3 次

更新成功! 
更新成功!
更新成功!
  • 1
  • 2
  • 3

提示:若去掉 for each row,再执行上述操作,则仅触发 1

2.2 follows

前提:触发器的执行是否需要指定 '先后顺序'1. 若不需要,则无需 follows 关键字 
2. 若需要   (1) before 和 after 能否区分,若能,则无需 follows 关键字   (2) 最后,才用 follows 区分
  • 1
  • 2
  • 3
/*触发器1:*/

create or replace trigger scott.tr_ai_student_info_1
   after insert on scott.student_info
   for each row
begin
   if inserting then
      dbms_output.put_line('插入操作 1');
   end if;
end;
/

/*触发器2:(顺序:先触发器1,再触发器2)*/

create or replace trigger scott.tr_ai_student_info_2
   after insert on scott.student_info
   for each ROW
   FOLLOWS scott.tr_ai_student_info_1
begin
   if inserting then
      dbms_output.put_line('插入操作 2');
   end if;
end;
/

/*测试语句:*/

insert into scott.student_info(sno, name, sex) values(5, '赵六', '女');

/*PL/SQL 输出窗口:*/
插入操作 1
插入操作 2
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

2.3 when

1. when:增加触发条件
2. when 中的 new、old 是不带 : 的    (不是 :new、:old)
  • 1
  • 2
create or replace trigger scott.tr_ad_student_info
   after delete on scott.student_info
   for each row
   when (old.sno = 1)   -- sno = 1 的记录禁止被删除!
begin
   if deleting then
      raise_application_error(-20001, '此条记录禁止删除,sno = ' || :old.sno);
   end if;
end;
/

/*测试语句:*/
delete from scott.student_info t where t.sno = 1;

/*测试结果*/
弹框 - 错误提醒


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

2.3.查询触发器

--权限范围,由大到小:dba_* > all_* > user_* 
select * from dba_triggers; 
select * from all_triggers; 
select * from user_triggers;
  • 1
  • 2
  • 3
  • 4

2.5.删除触发器

drop trigger 触发器名;
select 'DROP TRIGGER ' || t.owner || '.' || t.trigger_name || ';' 删除
  from all_triggers t
 where t.owner = 'SCOTT'
   and t.table_name = 'STUDENT_INFO';
  • 1
  • 2
  • 3
  • 4
  • 5

2.6.常用属性

2.6.1 inserting、updating、deleting
1. 前提条件:无 
2. 表示含义
   inserting = insert 操作
   updating  = update 操作
   deleting  = delete 操作
  • 1
  • 2
  • 3
  • 4
  • 5
2.4.2 now、old
1. 前提条件:for each row
2. 表示含义
   :new = 触发前的值
   :old = 触发后的值
3. 说明 
   (1) new、old 均为 '默认值', 常用, 一般无需更改
       referencing new as new old as old       
   (2) 若想要更改,如:new => new_new,old => old_old  
       referencing new as new_new old as old_old
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

:new、:old 值分布情况:

insertupdatedelete
:new×
:old×
create or replace trigger scott.tr_au_student_info
   after update on scott.student_info
   for each row
begin
   -- 旧值
   dbms_output.put_line('old.sno = ' || :old.sno);
   dbms_output.put_line('old.name = ' || :old.name);
   dbms_output.put_line('old.sex = ' || :old.sex);
   dbms_output.put_line('------');
   -- 新值
   dbms_output.put_line('new.sno = ' || :new.sno);
   dbms_output.put_line('new.name = ' || :new.name);
   dbms_output.put_line('new.sex = ' || :new.sex);
end;
/

--测试语句:
update scott.student_info t
   set t.name = 'name',
       t.sex = '2'
 where t.sno = 1;
 
--测试结果:PL/SQL 输出窗口
old.sno = 1
old.name = 张三
old.sex =------
new.sno = 1
new.name = name
new.sex = 2
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

当一个基表被修改( INSERT, UPDATE, DELETE)时要执行的存储过程,执行时根据其所依附的基表改动而自动触发,因此与应用程序无关,用数据库触发器可以保证数据的一致性和完整性。

BEFORE INSERT

BEFORE INSERT FOR EACH ROW

AFTER INSERT

AFTER INSERT FOR EACH ROW

BEFORE UPDATE

BEFORE UPDATE FOR EACH ROW

AFTER UPDATE

AFTER UPDATE FOR EACH ROW

BEFORE DELETE

BEFORE DELETE FOR EACH ROW

AFTER DELETE

AFTER DELETE FOR EACH ROW
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

总结

3.1 触发器触发次序

  1. 执行 BEFORE语句级触发器;

  2. 对与受语句影响的每一行:

​ 执行 BEFORE行级触发器

​ 执行 DML语句

​ 执行 AFTER行级触发器

  1. 执行 AFTER语句级触发器

3.2 创建DML触发器

触发器名与过程名和包的名字不一样,它是单独的名字空间,因而触发器名可以和表或过程有相同的名字,但在一个模式中触发器名不能相同。

DML触发器的限制

​ CREATE TRIGGER语句文本的字符长度不能超过32KB;

​ 触发器体内的SELECT 语句只能为SELECT … INTO …结构,或者为定义游标所使用的SELECT 语句。

​ 触发器中不能使用数据库事务控制语句 COMMIT; ROLLBACK, SVAEPOINT 语句;

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

​ 触发器中不能使用LONG, LONG RAW 类型;

​ 触发器内可以参照LOB 类型列的列值,但不能通过 :NEW 修改LOB列中的数据;

DML触发器基本要点

​ **触发时机:**指定触发器的触发时间。如果指定为BEFORE,则表示在执行DML操作之前触发,以便防止某些错误操作发生或实现某些业务规则;如果指定为AFTER,则表示在执行DML操作之后触发,以便记录该操作或做某些事后处理。

​ **触发事件:**引起触发器被触发的事件,即DML操作(INSERT、UPDATE、DELETE)。既可以是单个触发事件,也可以是多个触发事件的组合(只能使用OR逻辑组合,不能使用AND逻辑组合)。

​ **条件谓词:**当在触发器中包含多个触发事件(INSERT、UPDATE、DELETE)的组合时,为了分别针对不同的事件进行不同的处理,需要使用ORACLE提供的如下条件谓词。

INSERTING : 当触发事件是INSERT时,取值为TRUE,否则为FALSE。

**UPDATING : [(column_1,column_2,…,column_x)]:**当触发事件是UPDATE 时,如果修改了column_x列,则取值为TRUE,否则为FALSE。其中column_x是可选的。

DELETING : 当触发事件是DELETE时,则取值为TRUE,否则为FALSE。

​ **解发对象:**指定触发器是创建在哪个表、视图上。

​ **触发类型:**是语句级还是行级触发器。

​ **触发条件:**由WHEN子句指定一个逻辑表达式,只允许在行级触发器上指定触发条件,指定UPDATING后面的列的列表。

问题:当触发器被触发时,要使用被插入、更新或删除的记录中的列值,有时要使用操作前、 后列的值.

实现: :NEW 修饰符访问操作完成后列的值

​ :OLD 修饰符访问操作完成前列的值

特性INSERTUPDATEDELETE
OLDNULL实际值实际值
NEW实际值实际值NULL

例1: 建立一个触发器, 当职工表 emp 表被删除一条记录时,把被删除记录写到职工表删除日志表中去。

CREATE TABLE emp_his AS SELECT * FROM EMP WHERE **1**=**2**;

  CREATE OR REPLACE TRIGGER tr_del_emp   BEFORE DELETE --指定触发时机为删除操作前触发  

  ON scott.emp   FOR EACH ROW  --说明创建的是行级触发器

BEGIN  --将修改前数据插入到日志记录表 del_emp ,以供监督使用。  

  INSERT INTO emp_his(deptno , empno, ename , job ,mgr , sal , comm , hiredate )    

  VALUES( :old.deptno, :old.empno, :old.ename , :old.job,:old.mgr, :old.sal, :old.comm, :old.hiredate );

END;

  DELETE emp WHERE empno=**7788**;

  DROP TABLE emp_his;

  DROP TRIGGER del_emp;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

**例2:**限制对Departments表修改(包括INSERT,DELETE,UPDATE)的时间范围,即不允许在非工作时间修改departments表。

CREATE OR REPLACE TRIGGER tr_dept_time

  BEFORE INSERT OR DELETE OR UPDATE ON departments

  BEGIN IF (TO_CHAR(sysdate,'DAY') IN ('星期六', '星期日'))

  OR (TO_CHAR(sysdate, 'HH24:MI') NOT BETWEEN '08:30' AND '18:00')

THEN   RAISE_APPLICATION_ERROR(-**20001**, '不是上班时间,不能修改departments表');

END IF;

END;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号