Oracle触发器trigger详解

前端之家收集整理的这篇文章主要介绍了Oracle触发器trigger详解前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

触发器相关概念及语法

概述

本篇博文中主要探讨以下内容

什么是触发器
触发器的应用场景
触发器的语法
触发器的类型
案例

数据:

这里写图片描述

触发器的概念和第一个触发器

数据库触发器是一个与表相关联的,存储的PL/sql 语句。

每当一个特定的数据操作语句(insert update delete)在指定的表上发出时,Oracle自动执行触发器中定义的语句序列。

举个简单的例子:

当员工表中新增一条记录后,自动打印“成功插入新员工”

sql;"> create or replace trigger insertStaffHint after insert on xgj_test for each row declare -- local variables here begin

dbms_output.put_line('新增员工成功');

end insertStaffHint;

这里写图片描述

触发器的应用场景

复杂的安全性检查
数据的确认
数据库审计
数据的备份和审计

触发器的语法

sql;"> 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 行触发器和其它类型的触发器中。

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

每张表最多可建立12 种类型的触发器:

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

触发器的类型

行触发器要求当一个DML语句操作影响数据库中的多行数据时,对于其中的每个数据行,只要它们符合触发约束条件,均激活一次触发器; 在行级触发器中,使用:old 和:new 伪记录变量,识别值的状态

语句触发器将整个语句操作作为触发事件,当它符合约束条件时,激活一次触发器。

当省略FOR EACH ROW 选项时,BEFORE 和AFTER 触发器为语句触发器,
而INSTEAD OF 触发器则只能为行触发器。

其他规则

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

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提供的如下条件谓词。
1).INSERTING:当触发事件是INSERT时,取值为TRUE,否则为FALSE。
2).UPDATING [(column_1,column_2,…,column_x)]:当触发事件是UPDATE 时,如果修改了column_x列,则取值为TRUE,否则为FALSE。其中column_x是可选的。
3).DELETING:当触发事件是DELETE时,则取值为TRUE,否则为FALSE。
解发对象:指定触发器是创建在哪个表、视图上。
触发类型:是语句级还是行级触发器
触发条件:由WHEN子句指定一个逻辑表达式,只允许在行级触发器上指定触发条件,指定UPDATING后面的列的列表。

触发器应用

触发器应用一 复杂的安全性检查

禁止在非工作时间插入数据

sql;"> /** 非工作时间(星球六 星期日, 非9点~18点的区间) 禁止写入数据

首先要搞清楚: 触发器的类型--语句级触发器。
不管插入了多少条数据,没有必要对每一行数据都进行校验,只要不在这个时间段内,都不让插入。
*/
create or replace trigger addStafffCheck
before insert on xgj_test

declare
-- local variables here
begin

if to_char(sysdate,'day') in ('星期六','星期日') or
to_number(to_char(sysdate,'hh24')) not between 9 and 18 then
--禁止insert
raise_application_error(-20001,'非工作时间禁止插入数据');
end if;
end addStafffCheck;

这里写图片描述

用于在plsql使用程序中自定义不正确消息。
该异常只好在数据库端的子程序(流程、函数、包、触发器)中运用,而无法在匿名块和客户端的子程序中运用。
语法为raise_application_error(error_number,message[,[truefalse]]); 其中 error_number用于定义不正确号,
该不正确号必须在-20000到-20999之间的负整数; message用于指定不正确消息,并且该消息的长度无法超过2048字节;

触发器应用二

数据确认

工资不能越涨越少

分析: 首先确认下触发器的类型, 因为需要对每一条数据进行确认,所以是个行级触发器。 需要for each row

sql;"> /** 涨后的薪水不能低于涨前的薪水

1 :old 和 :new 代表同一条记录
2 :old 代表操作该行之前,这一行的值
:new 代表操作该行之后,这一行的值
*/

create or replace trigger checkSalary
before update
on xgj_test
for each row
declare
-- local variables here 没有变量声明的话,declare可以省略
begin

--- if 涨后的薪水 < 涨前的薪水 then 如何表示呢 ?
if :new.sal < :old.sal then

raise_application_error(-20002,'涨后的薪水:'|| :new.sal ||'小于涨前的薪水:'||:old.sal);
end if;
end checkSalary;

这里写图片描述

触发器应用三

数据库审计

创建基于值的触发器

if :new.sal > 6000 then
insert into xgj_record values(:new.sal ||'-'|| :new.username ||'-'|| :new.job);
end if;

end addrecord;

这里写图片描述

触发器应用四

数据的备份和同步

当给员工涨完工资后,自动备份到备份表中

sql;"> create table xgj_test_bak as select * from xgj_test ;

create or replace trigger databack
after update
on xgj_test
for each row

begin

update xgj_test_bak set sal = :new.sal where username = :new.username ;

end databack;

这里写图片描述

oracle中还有个利用快照备份,是异步的。 而利用触发器,是同步的。

其他

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

sql;"> 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;

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

sql;"> 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;

限定只对部门号为80的记录进行行触发器操作。

sql;"> CREATE OR REPLACE TRIGGER tr_emp_sal_comm BEFORE UPDATE OF salary,commission_pct OR DELETE ON HR.employees FOR EACH ROW WHEN (old.department_id = 80) BEGIN CASE WHEN UPDATING ('salary') THEN IF :NEW.salary < :old.salary THEN

RAISE_APPLICATION_ERROR(-20001,'部门80的人员的工资不能降');
END IF;
WHEN UPDATING ('commission_pct') THEN

IF :NEW.commission_pct < :old.commission_pct THEN
RAISE_APPLICATION_ERROR(-20002,'部门80的人员的奖金不能降');
END IF;
WHEN DELETING THEN
RAISE_APPLICATION_ERROR(-20003,'不能删除部门80的人员记录');
END CASE;
END;

/
实例:
UPDATE employees SET salary = 8000 WHERE employee_id = 177;
DELETE FROM employees WHERE employee_id in (177,170);
/

利用行触发器实现级联更新。在修改了主表regions中的region_id之后(AFTER),级联的、自动的更新子表countries表中原来在该地区的国家的region_id。

sql;"> CREATE OR REPLACE TRIGGER tr_reg_cou AFTER update OF region_id ON regions FOR EACH ROW BEGIN DBMS_OUTPUT.PUT_LINE('旧的region_id值是'||:old.region_id ||'、新的region_id值是'||:new.region_id); UPDATE countries SET region_id = :new.region_id WHERE region_id = :old.region_id; END;

在触发器中调用过程。

sql;"> CREATE OR REPLACE PROCEDURE add_job_history ( p_emp_id job_history.employee_id%type,p_start_date job_history.start_date%type,p_end_date job_history.end_date%type,p_job_id job_history.job_id%type,p_department_id job_history.department_id%type ) IS BEGIN INSERT INTO job_history (employee_id,start_date,end_date,job_id,department_id) VALUES(p_emp_id,p_start_date,p_end_date,p_job_id,p_department_id); END add_job_history;

--创建触发器调用存储过程...
CREATE OR REPLACE TRIGGER update_job_history
AFTER UPDATE OF job_id,department_id ON employees
FOR EACH ROW
BEGIN
add_job_history(:old.employee_id,:old.hire_date,sysdate,:old.job_id,:old.department_id);
END;

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程之家。

猜你在找的Oracle相关文章