zoukankan      html  css  js  c++  java
  • plsql programming 19 触发器

    image

    image

    image

    挂起语句, 是指数据库 Hang 到那不能动了, 触发的.

    1. DML 触发器

    这种类型的触发器对于开发人员都很常见, 其他类型的触发器主要是给DBA使用的.

    配置触发器,我们需要回答以下问题:

    • 触发器应该是对整个DML语句触发一次, 还是要为语句涉及的每一行都触发一次 ?
    • 触发器是应该在整个语句开始之前或者结束之后, 或者是在对每一行记录处理之前或者之后触发 ?
    • 触发器到底是由插入, 更新, 删除或者是某种组合触发的 ? (oracle 11g 开始支持多个操作组合触发器)

    image

    如果我在 books 表上定义了一个行级的更新触发器, 这个触发器就会被触发 1000 次.

    image

    image

    提示:如果我们把 DML 触发器定义成自治事务, 则在触发器中执行的所有DML语句都可以保存或者回滚-使用显示的 commit 或者 rollabck 语句- 不会影响到主事务.

    千万不要这么做, 记住, 自治事务不是一个好东西, 只有在两个地方可以使用它.

    1. 你想演示某种现象, 用来演示的, 没有实际意义.

    2. 你想记录下所有的修改, 即便是没有成功提交的修改, 你也想知道, 这时单独使用一个自治事务, 自治事务内部记录修改的动作, 然后自治事务单独commit.

    image

     1 -- chap19_01.sql
     2 create or replace trigger validate_employee_changes
     3     after 
     4     insert or update on employees
     5     for each row
     6 begin
     7     check_date(:NEW.hire_date);
     8     check_email(:NEW.email);
     9 end;
    10 /
    11 show errors;
    12 
    13 -- chap19_02.sql
    14 create or replace trigger show_insert_ttt
    15     after
    16     insert on ttt
    17     for each row
    18 begin
    19     dbms_output.put_line('You just now insert some rows');
    20 end;
    21 /
    22 
    23 -- chap19_03.sql
    24 create or replace trigger bef_ins_ceo_comp
    25     before 
    26     insert on ceo_compensation
    27     for each row
    28 declare
    29     pragma autonomous_transaction;    -- 自治事务的意思
    30 begin
    31     insert into ceo_comp_history
    32     values(:NEW.name, :OLD.compensation,
    33            :NEW.compensation, 'After insert', SYSDATE);
    34     commit;        -- 自治事务可以 commit;
    35 end;
    36 /
    37 show errors;
    38 
    39 -- chap19_04.sql
    40 create or replace trigger check_raise
    41     after
    42     update of salary on employees
    43     for each row
    44     when ((OLD.salary != NEW.salary) OR (OLD.salary IS NULL AND NEW.salary IS NOT NULL) OR
    45           (OLD.salary is not null and NEW.salary is null))
    46 begin
    47     -- ...
    48 end;
    49 /
    50 show errors;

    注意下边, 只是在有 WHEN 子句时, 有关WHEN 子句需要注意的内容,

    image

    image

    NEW 和 OLD 之所以说是伪记录, 是因为它们不具有真正的PL/SQL记录应该有的属性, OLD保留的是要处理记录的原始值, NEW代表的是新的值. 这些记录和使用该表的%rowtype属性声明的记录结构完全相同.

    image

    可以使用 referencing 子句来修改 new 和 old 的名字, 但是个人感觉用处不大.

     1 create or replace trigger check_raise
     2     after
     3     update of salary on employees
     4     for each row
     5     when ((OLD.salary != NEW.salary) OR (OLD.salary IS NULL AND NEW.salary IS NOT NULL) OR
     6           (OLD.salary is not null and NEW.salary is null))
     7 begin
     8     -- ...
     9 end;
    10 /
    11 show errors;
    12 
    13 -- chap19_05.sql
    14 create or replace trigger audit_update
    15     after
    16     update on frame
    17     referencing old as prior_to_cheat NEW as after_cheat  -- 修改old 和 new 的名字, 但是个人认为用处不大
    18     for each row
    19 begin
    20     insert into frame_audit ( bowler_id, game_id, old_score,
    21                               new_score, change_date, operation)
    22                      values (:after_cheat.bowler_id, :after_cheat.game_id, :prior_to_cheat.score,
    23                              :after_cheat.score, SYSDATE, 'UPDATE');
    24 end;
    25 /
    26 show errors;

    image

    image

    image

    image

     1 create or replace trigger three_for_the_price_of_one
     2     before
     3     delete or insert or update on account_transaction
     4     for each row
     5 begin
     6     -- 判断insert
     7     if inserting then
     8         :new.created_by := user;
     9         :new.created_date := sysdate;
    10     elsif deleting then
    11         audit_deletion(user, sysdate);
    12     elsif updating then
    13         :new.updated_by := user;
    14         :new.updated_date := sysdate;
    15     end if;
    16 end;
    17 /
    18 show errors;

    另外, 针对 updating 还有专门确认是不是专门针对某一列的更改, updating('column'), 用这个判断, 即便是修改操作, 但是不是修改这列也不会触发相应操作.

    触发器大例子

    Trustful 夫人经营着一家保龄球馆, 一直接到投诉说有人对比分做手脚, 最近她安装了一个计分系统, 想通过这个系统抓住那些作弊者.

     1 create table frame(
     2     bowler_id    number,
     3     game_id        number,
     4     frame_number    number,
     5     strike        varchar2(1)    default 'N',
     6     spare        varchar2(1)    default 'N',
     7     constraint    frame_pk
     8     primary key (bowler_id, game_id, frame_number)
     9 );
    10 
    11 create table frame_audit(
    12     bowler_id    number,
    13     game_id        number,
    14     frame_number    number,
    15     old_strike    varchar2(1),
    16     new_strike    varchar2(1),
    17     old_spare    varchar2(1),
    18     new_spare    varchar2(1),
    19     old_score    number,
    20     new_score    number,
    21     change_date    date,
    22     operation    varchar2(6)
    23 );
     1 create or replace trigger
     2     after
     3     insert or update or delete on frame
     4     for each row
     5 begin
     6     if inserting then
     7         insert into frame_audit(bowler_id, game_id, frame_number,
     8                                 new_strike, new_spare, new_score,
     9                                 change_date, operation)
    10                         values (:NEW.bowler_id, :NEW.game_id, :NEW.frame_number,
    11                                 :NEW.strike, :NEW.spare, :NEW.score,
    12                                 SYSDATE, 'INSERT');
    13     elsif updating then
    14         insert into frame_audit(bowler_id, game_id, frame_number,
    15                                 old_strike, new_strike,
    16                                 old_spare, new_spare,
    17                                 old_score, new_score,
    18                                 change_date, operation)
    19                         values (:NEW.bowler_id, :NEW.game_id, :NEW.frame_number,
    20                                 :OLD.strike, :NEW.strike,
    21                                 :OLD.spare, :NEW.spare, 
    22                                 :OLD.score, :NEW.score,
    23                                 SYSDATE, 'UPDATE'); 
    24     elsif deleting then
    25         insert into frame_audit(bowler_id, game_id, frame_number,
    26                                 old_strike, old_spare, old_score,
    27                                 change_date, operation)
    28                         values (:OLD.bowler_id, :OLD.game_id, :OLD.frame_number,
    29                                 :OLD.strike, :OLD.spare, :OLD.score,
    30                                 SYSDATE, 'DELETE');
    31     end if;
    32 end audit_frames;
    33 /
    34 show errors;

    上例中的 update 可以替换的更精确一些, 例如 :

     1 create or replace trigger audit_update
     2     after
     3     update of strike, spare, score of frame  -- 具体指定了有哪些列
     4     for each row
     5     when (old.strike != new.strike or 
     6           old.spare != new.spare or
     7           old.score != new.score)
     8 begin
     9     -- ...
    10 end;
    11 /
    12 show errors;

    多个触发器执行的顺序, 从 oracle 11g 开始可以通过 follows 子句来保证. 例如:

     1 create or replace trigger increment_by_two
     2     before
     3     insert on incremented_values
     4     for each row
     5     follows incretment_by_one   -- 另一个触发器的名字
     6 begin
     7     if :new.value_increamentd > 1 then
     8         :new.value_incrementd := :new.value_incremented + 2;
     9     end if;
    10 end;

    如果触发器的内容是修改了本表(即触发该触发器的表), 那么可能存在问题, 所以给出一些指导:

    • 通常来说, 行级触发器不应该去读或者写导致它被触发的表的内容, 不过这个约束只针对行级触发器, 语句级触发器可以随意读写其触发表的内容, 这也是避免"突变表"错误的一种方法.
    • 如果我们的触发器使用自治事务(通过使用 PRAGMA AUTONOMOUS TRANSACTION语句, 以及在触发器体内执行了提交), 我们就可以查询触发表的内容, 不过我们仍然不能修改表的内容.

    下边的内容, 复合触发器, 是 oracle 11g 以后才支持的.

    image

    image

    个人感觉复合触发器不好, 暂时忽略吧. 还是分开写好

    image

    image

    image

    1 create or replace trigger town_cirer
    2     after
    3     create on schema        -- 创建所有的对象都会触发, 不仅仅是 table
    4 begin
    5     dbms_ouput.put_line('I believe you have created something!');
    6 end;
    7 /
    8 show errors;

    image

    可以提供一些属性, 来方便查看创建了那些对象等等.

    image

    image

    image

    image

    使用事件和属性

    1 create or replace trigger no_create
    2     after
    3     create on schema
    4 begin
    5     raise_application_error( -20000, 'Error: Objects cannot be created in the production database.');
    6 end;
    7 /
    8 show errors;

    上例执行以后, 所有的创建对象都不可以执行.(要知道触发器是事务的一部分)
    利用属性:

     1 create or replace trigger no_create
     2     after
     3     create on schema
     4 begin
     5     raise_application_error(-20000, 
     6                             'Cannot create the' || ORA_DICT_OBJ_TYPE ||
     7                             'named '            || ORA_DICT_OBJ_NAME ||
     8                             'as requested by'    || ORA_DICT_OBJ_OWNER ||
     9                             ' in production.');
    10 end;
    11 /
    12 show errors;
     1 create or replace trigger no_create
     2     before
     3     ddl on schema
     4 begin
     5     if ora_sysevent = 'CREATE' then
     6         raise_application_error(-20000, 
     7                             'Cannot create the' || ORA_DICT_OBJ_TYPE ||
     8                             'named '            || ORA_DICT_OBJ_NAME ||
     9                             'as requested by'    || ORA_DICT_OBJ_OWNER ||
    10                             ' in production.');
    11     elsif ora_sysevent = 'DROP' then
    12         -- ...
    13     end if;
    14 end;
    15 /
    16 show errors;

    删除不可删除的, 特殊处理

    比如你创建了一个不能删除一些对象的触发器, 那么完蛋了, 以后你再也别想删除任何对象了, 因为这个限制删除的触发器本身就是个对象, 但是 oracle 提供的是, 你可以删除这个触发器对象

     1 create or replace trigger undroppable
     2     before
     3     drop on schema
     4 begin
     5     raise_application_error(-20000, 'You cannot drop me! I am invincible!');
     6 end;
     7 /
     8 show errors;
     9 
    10 -- 上边的不能删除任何对象的触发器创建好了, 你不可以删除一般的对象了, 但是
    11 drop trigger undroppable;  -- 这条语句是可以顺利执行的

    image

    image

    这里要自己想一下, 例如没有 before startup 触发器, 这是肯定的, 还没startup 触发器怎么运行, 同样, 没有 after shutdown 触发器

    没有 before logon 触发器(这个含义是, 等着, 某人要来, 你怎么知道某人要来, 所以语义不通), 没有 after logoff 没有 before servererror 触发器 

    image

    image

    再例如 servererror 触发器

    1 create or replace trigger error_log
    2     after
    3     servererror on database
    4 begin
    5     central_error_log.log_error;  -- 调用包
    6 end;

    INSTEAD OF 触发器

    image

    主要的需求是, 无法直接对视图进行插入, 删除等操作.

    例子:

       1:  create or replace trigger error_log
       2:      after
       3:      servererror on database
       4:  begin
       5:      central_error_log.log_error;  -- 调用包
       6:  end;
       7:   
       8:  -- chap19_16.sql
       9:  create table delivery(
      10:      delivery_id    number,
      11:      delivery_start date,
      12:      delivery_end    date,
      13:      area_id        number,
      14:      driver_id    number
      15:  );
      16:   
      17:  create table area(
      18:      area_id    number,
      19:      area_desc    varchar2(30)
      20:      );
      21:      
      22:  create table driver(
      23:      driver_id    number,
      24:      driver_name    varchar2(30)
      25:      );
      26:      
      27:  create sequence delivery_id_seq;
      28:  create sequence area_id_seq;
      29:  create sequence driver_id_seq;
      30:   
      31:  create or replace view delivery_info 
      32:  as
      33:  select d.delivery_id,
      34:          d.delivery_start,
      35:          d.delivery_end,
      36:          a.area_desc,
      37:          dr.driver_name
      38:    from delivery d,
      39:          area a,
      40:          driver dr
      41:   where a.area_id = d.area_id
      42:     and dr.driver_id = d.driver_id
       1:  create or replace trigger delivery_info_insert
       2:      instead of insert
       3:      on delivery_info
       4:  declare
       5:      cursor curs_get_driver_id(cp_driver_name varchar2)
       6:      is
       7:          select driver_id
       8:            from driver
       9:           where driver_name = cp_driver_name;
      10:      v_driver_id    number;
      11:      
      12:  begin
      13:      -- ...
      14:  end;
      15:  /

    AFTER SUSPEND 触发器 (挂起触发器)

    image

    image

    image

    另外还提供了很多函数, 关于挂起的内容, 个人感觉, 这个地方可以忽略

    管理触发器

    alter trigger trigger_name disable;

    alter trigger trigger_name enable;

    drop trigger trigger_name;

    查看触发器

    DBA_TRIGGERS

    ALL_TRIGGERS

    USER_TRIGGERS

    检查触发器的有效性

    image

  • 相关阅读:
    Centos配置Apache phpadmin环境
    Linux添加FTP用户并设置权限
    Java中的过滤器
    【eclipse】注释模版
    [ci db操作]
    给vmware的Linux虚拟机添加硬盘
    基于LVDS/M-LVDS的数据通信
    如何找回丢失的硬盘分区表?
    vc++怎么可以直接刷掉MBR?搞笑的吧
    EFI、UEFI、MBR、GPT的区别
  • 原文地址:https://www.cnblogs.com/moveofgod/p/3465380.html
Copyright © 2011-2022 走看看