ORACLE触发器详解

创建触发器
创建触发器的一般语法是:

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)时要执行的存储过程,执行时根据其所依附的基表改动而自动触发,因此与应用程序无关,用数据库触发器可以保证数据的一致性和完整性。

触发器实例详解:

例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;
例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;

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

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);
*/

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

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;
例5:在触发器中调用过程。

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;
删除和使能触发器
l 删除触发器:
DROP TRIGGER trigger_name;
当删除其他用户模式中的触发器名称,需要具有DROP ANY TRIGGER系统权限,当删除建立在数据库上的触发器时,用户需要具有ADMINISTER DATABASE TRIGGER系统权限。
此外,当删除表或视图时,建立在这些对象上的触发器也随之删除。
l 禁用或启用触发器
数据库TRIGGER 的状态:
有效状态(ENABLE):当触发事件发生时,处于有效状态的数据库触发器TRIGGER 将被触发。
无效状态(DISABLE):当触发事件发生时,处于无效状态的数据库触发器TRIGGER 将不会被触发,此时就跟没有这个数据库触发器(TRIGGER) 一样。
数据库TRIGGER的这两种状态可以互相转换。格式为:
ALTER TIGGER trigger_name [DISABLE | ENABLE ];

–例:ALTER TRIGGER emp_view_delete DISABLE;

       ALTER TRIGGER语句一次只能改变一个触发器的状态,而ALTER TABLE语句则一次能够改变与指定表相关的所有触发器的使用状态。格式为:             

ALTER TABLE [schema.]table_name {ENABLE|DISABLE} ALL TRIGGERS;

–例:使表EMP 上的所有TRIGGER 失效:
ALTER TABLE emp DISABLE ALL TRIGGERS;

8.4 触发器和数据字典
相关数据字典:USER_TRIGGERS、ALL_TRIGGERS、DBA_TRIGGERS
SELECT TRIGGER_NAME, TRIGGER_TYPE, TRIGGERING_EVENT,
TABLE_OWNER, BASE_OBJECT_TYPE, REFERENCING_NAMES,
STATUS, ACTION_TYPE
FROM user_triggers;

8.5 数据库触发器的应用举例
例1:创建一个DML语句级触发器,当对emp表执行INSERT, UPDATE, DELETE 操作时,它自动更新dept_summary 表中的数据。由于在PL/SQL块中不能直接调用DDL语句,所以,利用ORACLE内置包DBMS_UTILITY中的EXEC_DDL_STATEMENT过程,由它执行DDL语句创建触发器。

CREATE TABLE dept_summary(
Deptno NUMBER(2),
Sal_sum NUMBER(9, 2),
Emp_count NUMBER);

INSERT INTO dept_summary(deptno, sal_sum, emp_count)
SELECT deptno, SUM(sal), COUNT(*)
FROM emp
GROUP BY deptno;

–创建一个PL/SQL过程disp_dept_summary
–在触发器中调用该过程显示dept_summary标中的数据。
CREATE OR REPLACE PROCEDURE disp_dept_summary
IS
Rec dept_summary%ROWTYPE;
CURSOR c1 IS SELECT * FROM dept_summary;
BEGIN
OPEN c1;
FETCH c1 INTO REC;
DBMS_OUTPUT.PUT_LINE(‘deptno sal_sum emp_count’);
DBMS_OUTPUT.PUT_LINE(‘————————————-‘);
WHILE c1%FOUND LOOP
DBMS_OUTPUT.PUT_LINE(RPAD(rec.deptno, 6)||
To_char(rec.sal_sum, ‘$999,999.99’)||
LPAD(rec.emp_count, 13));
FETCH c1 INTO rec;
END LOOP;
CLOSE c1;
END;
BEGIN
DBMS_OUTPUT.PUT_LINE(‘插入前’);
Disp_dept_summary();
DBMS_UTILITY.EXEC_DDL_STATEMENT(’
CREATE OR REPLACE TRIGGER trig1
AFTER INSERT OR DELETE OR UPDATE OF sal ON emp
BEGIN
DBMS_OUTPUT.PUT_LINE(”正在执行trig1 触发器…”);
DELETE FROM dept_summary;
INSERT INTO dept_summary(deptno, sal_sum, emp_count)
SELECT deptno, SUM(sal), COUNT(*)
FROM emp GROUP BY deptno;
END;
‘);

INSERT INTO dept(deptno, dname, loc)
VALUES(90, ‘demo_dept’, ‘none_loc’);
INSERT INTO emp(ename, deptno, empno, sal)
VALUES(USER, 90, 9999, 3000);

DBMS_OUTPUT.PUT_LINE(‘插入后’);
Disp_dept_summary();

UPDATE emp SET sal=1000 WHERE empno=9999;
DBMS_OUTPUT.PUT_LINE(‘修改后’);
Disp_dept_summary();

DELETE FROM emp WHERE empno=9999;
DELETE FROM dept WHERE deptno=90;

DBMS_OUTPUT.PUT_LINE(‘删除后’);
Disp_dept_summary();
DBMS_UTILITY.EXEC_DDL_STATEMENT(‘DROP TRIGGER trig1’);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE||’—’||SQLERRM);

END;

例2:创建DML语句行级触发器。当对emp表执行INSERT, UPDATE, DELETE 操作时,它自动更新dept_summary 表中的数据。由于在PL/SQL块中不能直接调用DDL语句,所以,利用ORACLE内置包DBMS_UTILITY中的EXEC_DDL_STATEMENT过程,由它执行DDL语句创建触发器。

BEGIN
DBMS_OUTPUT.PUT_LINE(‘插入前’);
Disp_dept_summary();
DBMS_UTILITY.EXEC_DDL_STATEMENT(
‘CREATE OR REPLACE TRIGGER trig2_update
AFTER UPDATE OF sal ON emp
REFERENCING OLD AS old_emp NEW AS new_emp
FOR EACH ROW
WHEN (old_emp.sal != new_emp.sal)
BEGIN
DBMS_OUTPUT.PUT_LINE(”正在执行trig2_update 触发器…”);
DBMS_OUTPUT.PUT_LINE(”sal 旧值:”|| :old_emp.sal);
DBMS_OUTPUT.PUT_LINE(”sal 新值:”|| :new_emp.sal);
UPDATE dept_summary
SET sal_sum=sal_sum + :new_emp.sal - :old_emp.sal
WHERE deptno = :new_emp.deptno;
END;’
);

DBMS_UTILITY.EXEC_DDL_STATEMENT(
‘CREATE OR REPLACE TRIGGER trig2_insert
AFTER INSERT ON emp
REFERENCING NEW AS new_emp
FOR EACH ROW
DECLARE
I NUMBER;
BEGIN
DBMS_OUTPUT.PUT_LINE(”正在执行trig2_insert 触发器…”);
SELECT COUNT(*) INTO I
FROM dept_summary WHERE deptno = :new_emp.deptno;
IF I > 0 THEN
UPDATE dept_summary
SET sal_sum=sal_sum+:new_emp.sal,
Emp_count=emp_count+1
WHERE deptno = :new_emp.deptno;
ELSE
INSERT INTO dept_summary
VALUES (:new_emp.deptno, :new_emp.sal, 1);
END IF;
END;’
);

DBMS_UTILITY.EXEC_DDL_STATEMENT(
‘CREATE OR REPLACE TRIGGER trig2_delete
AFTER DELETE ON emp
REFERENCING OLD AS old_emp
FOR EACH ROW
DECLARE
I NUMBER;
BEGIN
DBMS_OUTPUT.PUT_LINE(”正在执行trig2_delete 触发器…”);
SELECT emp_count INTO I
FROM dept_summary WHERE deptno = :old_emp.deptno;
IF I >1 THEN
UPDATE dept_summary
SET sal_sum=sal_sum - :old_emp.sal,
Emp_count=emp_count - 1
WHERE deptno = :old_emp.deptno;
ELSE
DELETE FROM dept_summary WHERE deptno = :old_emp.deptno;
END IF;
END;’
);

INSERT INTO dept(deptno, dname, loc)
VALUES(90, ‘demo_dept’, ‘none_loc’);
INSERT INTO emp(ename, deptno, empno, sal)
VALUES(USER, 90, 9999, 3000);
INSERT INTO emp(ename, deptno, empno, sal)
VALUES(USER, 90, 9998, 2000);
DBMS_OUTPUT.PUT_LINE(‘插入后’);
Disp_dept_summary();

UPDATE emp SET sal = sal*1.1 WHERE deptno=90;
DBMS_OUTPUT.PUT_LINE(‘修改后’);
Disp_dept_summary();

DELETE FROM emp WHERE deptno=90;
DELETE FROM dept WHERE deptno=90;
DBMS_OUTPUT.PUT_LINE(‘删除后’);
Disp_dept_summary();

DBMS_UTILITY.EXEC_DDL_STATEMENT(‘DROP TRIGGER trig2_update’);
DBMS_UTILITY.EXEC_DDL_STATEMENT(‘DROP TRIGGER trig2_insert’);
DBMS_UTILITY.EXEC_DDL_STATEMENT(‘DROP TRIGGER trig2_delete’);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE||’—’||SQLERRM);
END;

例3:利用ORACLE提供的条件谓词INSERTING、UPDATING和DELETING创建与例2具有相同功能的触发器。

BEGIN
DBMS_OUTPUT.PUT_LINE(‘插入前’);
Disp_dept_summary();
DBMS_UTILITY.EXEC_DDL_STATEMENT(
‘CREATE OR REPLACE TRIGGER trig2
AFTER INSERT OR DELETE OR UPDATE OF sal
ON emp
REFERENCING OLD AS old_emp NEW AS new_emp
FOR EACH ROW
DECLARE
I NUMBER;
BEGIN
IF UPDATING AND :old_emp.sal != :new_emp.sal THEN
DBMS_OUTPUT.PUT_LINE(”正在执行trig2 触发器…”);
DBMS_OUTPUT.PUT_LINE(”sal 旧值:”|| :old_emp.sal);
DBMS_OUTPUT.PUT_LINE(”sal 新值:”|| :new_emp.sal);
UPDATE dept_summary
SET sal_sum=sal_sum + :new_emp.sal - :old_emp.sal
WHERE deptno = :new_emp.deptno;
ELSIF INSERTING THEN
DBMS_OUTPUT.PUT_LINE(”正在执行trig2触发器…”);
SELECT COUNT(*) INTO I
FROM dept_summary
WHERE deptno = :new_emp.deptno;
IF I > 0 THEN
UPDATE dept_summary
SET sal_sum=sal_sum+:new_emp.sal,
Emp_count=emp_count+1
WHERE deptno = :new_emp.deptno;
ELSE
INSERT INTO dept_summary
VALUES (:new_emp.deptno, :new_emp.sal, 1);
END IF;
ELSE
DBMS_OUTPUT.PUT_LINE(”正在执行trig2触发器…”);
SELECT emp_count INTO I
FROM dept_summary WHERE deptno = :old_emp.deptno;
IF I > 1 THEN
UPDATE dept_summary
SET sal_sum=sal_sum - :old_emp.sal,
Emp_count=emp_count - 1
WHERE deptno = :old_emp.deptno;
ELSE
DELETE FROM dept_summary
WHERE deptno = :old_emp.deptno;
END IF;
END IF;
END;’
);

INSERT INTO dept(deptno, dname, loc)
VALUES(90, ‘demo_dept’, ‘none_loc’);
INSERT INTO emp(ename, deptno, empno, sal)
VALUES(USER, 90, 9999, 3000);
INSERT INTO emp(ename, deptno, empno, sal)
VALUES(USER, 90, 9998, 2000);
DBMS_OUTPUT.PUT_LINE(‘插入后’);
Disp_dept_summary();

UPDATE emp SET sal = sal*1.1 WHERE deptno=90;
DBMS_OUTPUT.PUT_LINE(‘修改后’);
Disp_dept_summary();

DELETE FROM emp WHERE deptno=90;
DELETE FROM dept WHERE deptno=90;
DBMS_OUTPUT.PUT_LINE(‘删除后’);
Disp_dept_summary();

DBMS_UTILITY.EXEC_DDL_STATEMENT(‘DROP TRIGGER trig2’);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE||’—’||SQLERRM);
END;

例4:创建INSTEAD OF 触发器。首先创建一个视图myview,由于该视图是复合查询所产生的视图,所以不能执行DML语句。根据用户对视图所插入的数据判断需要将数据插入到哪个视图基表中,然后对该基表执行插入操作。

DECLARE
No NUMBER;
Name VARCHAR2(20);
BEGIN
DBMS_UTILITY.EXEC_DDL_STATEMENT(’
CREATE OR REPLACE VIEW myview AS
SELECT empno, ename, ”E” type FROM emp
UNION
SELECT dept.deptno, dname, ”D” FROM dept
‘);
– 创建INSTEAD OF 触发器trigger3;
DBMS_UTILITY.EXEC_DDL_STATEMENT(’
CREATE OR REPLACE TRIGGER trig3
INSTEAD OF INSERT ON myview
REFERENCING NEW n
FOR EACH ROW
DECLARE
Rows INTEGER;
BEGIN
DBMS_OUTPUT.PUT_LINE(”正在执行trig3触发器…”);
IF :n.type = ”D” THEN
SELECT COUNT(*) INTO rows
FROM dept WHERE deptno = :n.empno;
IF rows = 0 THEN
DBMS_OUTPUT.PUT_LINE(”向dept表中插入数据…”);
INSERT INTO dept(deptno, dname, loc)
VALUES (:n.empno, :n.ename, ”none’’);
ELSE
DBMS_OUTPUT.PUT_LINE(”编号为”|| :n.empno||
”的部门已存在,插入操作失败!”);
END IF;
ELSE
SELECT COUNT(*) INTO rows
FROM emp WHERE empno = :n.empno;
IF rows = 0 THEN
DBMS_OUTPUT.PUT_LINE(‘’向emp表中插入数据…’’);
INSERT INTO emp(empno, ename)
VALUES(:n.empno, :n.ename);
ELSE
DBMS_OUTPUT.PUT_LINE(”编号为”|| :n.empno||
”的人员已存在,插入操作失败!”);
END IF;
END IF;
END;
‘);

INSERT INTO myview VALUES (70, 'demo', 'D');
INSERT INTO myview VALUES (9999, USER, 'E');
SELECT deptno, dname INTO no, name FROM dept WHERE deptno=70;
DBMS_OUTPUT.PUT_LINE('员工编号:'||TO_CHAR(no)||'姓名:'||name);
SELECT empno, ename INTO no, name FROM emp WHERE empno=9999;
DBMS_OUTPUT.PUT_LINE('部门编号:'||TO_CHAR(no)||'姓名:'||name);

DELETE FROM emp WHERE empno=9999;
DELETE FROM dept WHERE deptno=70;
DBMS_UTILITY.EXEC_DDL_STATEMENT(‘DROP TRIGGER trig3’);
END;

例5:利用ORACLE事件属性函数,创建一个系统事件触发器。首先创建一个事件日志表eventlog,由它存储用户在当前数据库中所创建的数据库对象,以及用户的登陆和注销、数据库的启动和关闭等事件,之后创建trig4_ddl、trig4_before和trig4_after触发器,它们调用事件属性函数将各个事件记录到eventlog数据表中。

BEGIN
– 创建用于记录事件日志的数据表
DBMS_UTILITY.EXEC_DDL_STATEMENT(’
CREATE TABLE eventlog(
Eventname VARCHAR2(20) NOT NULL,
Eventdate date default sysdate,
Inst_num NUMBER NULL,
Db_name VARCHAR2(50) NULL,
Srv_error NUMBER NULL,
Username VARCHAR2(30) NULL,
Obj_type VARCHAR2(20) NULL,
Obj_name VARCHAR2(30) NULL,
Obj_owner VARCHAR2(30) NULL
)
‘);

-- 创建DDL触发器trig4_ddl
DBMS_UTILITY.EXEC_DDL_STATEMENT('
    CREATE OR REPLACE TRIGGER trig4_ddl
        AFTER CREATE OR ALTER OR DROP 

ON DATABASE
DECLARE
Event VARCHAR2(20);
Typ VARCHAR2(20);
Name VARCHAR2(30);
Owner VARCHAR2(30);
BEGIN
– 读取DDL事件属性
Event := SYSEVENT;
Typ := DICTIONARY_OBJ_TYPE;
Name := DICTIONARY_OBJ_NAME;
Owner := DICTIONARY_OBJ_OWNER;
–将事件属性插入到事件日志表中
INSERT INTO scott.eventlog(eventname, obj_type, obj_name, obj_owner)
VALUES(event, typ, name, owner);
END;
‘);

-- 创建LOGON、STARTUP和SERVERERROR 事件触发器
DBMS_UTILITY.EXEC_DDL_STATEMENT('
    CREATE OR REPLACE TRIGGER trig4_after
        AFTER LOGON OR STARTUP OR SERVERERROR 
  ON DATABASE
    DECLARE
        Event VARCHAR2(20);
        Instance NUMBER;
        Err_num NUMBER;
        Dbname VARCHAR2(50);
        User VARCHAR2(30);
    BEGIN
        Event := SYSEVENT;
        IF event = ''LOGON'' THEN
            User := LOGIN_USER;
            INSERT INTO eventlog(eventname, username)
                VALUES(event, user);
        ELSIF event = ''SERVERERROR'' THEN
            Err_num := SERVER_ERROR(1);
            INSERT INTO eventlog(eventname, srv_error)
                VALUES(event, err_num);
        ELSE
            Instance := INSTANCE_NUM;
            Dbname := DATABASE_NAME;
            INSERT INTO eventlog(eventname, inst_num, db_name)
                VALUES(event, instance, dbname);
  END IF;
END;

‘);

– 创建LOGOFF和SHUTDOWN 事件触发器
DBMS_UTILITY.EXEC_DDL_STATEMENT(’
CREATE OR REPLACE TRIGGER trig4_before
BEFORE LOGOFF OR SHUTDOWN
ON DATABASE
DECLARE
Event VARCHAR2(20);
Instance NUMBER;
Dbname VARCHAR2(50);
User VARCHAR2(30);
BEGIN
Event := SYSEVENT;
IF event = ”LOGOFF” THEN
User := LOGIN_USER;
INSERT INTO eventlog(eventname, username)
VALUES(event, user);
ELSE
Instance := INSTANCE_NUM;
Dbname := DATABASE_NAME;
INSERT INTO eventlog(eventname, inst_num, db_name)
VALUES(event, instance, dbname);
END IF;
END;
‘);
END;

CREATE TABLE mydata(mydate NUMBER);
CONNECT SCOTT/TIGER

COL eventname FORMAT A10
COL eventdate FORMAT A12
COL username FORMAT A10
COL obj_type FORMAT A15
COL obj_name FORMAT A15
COL obj_owner FORMAT A10
SELECT eventname, eventdate, obj_type, obj_name, obj_owner, username, Srv_error
FROM eventlog;

DROP TRIGGER trig4_ddl;
DROP TRIGGER trig4_before;
DROP TRIGGER trig4_after;
DROP TABLE eventlog;
DROP TABLE mydata;

8.6 数据库触发器的应用实例
用户可以使用数据库触发器实现各种功能:
l 复杂的审计功能;
例:将EMP 表的变化情况记录到AUDIT_TABLE和AUDIT_TABLE_VALUES中。

CREATE TABLE audit_table(
Audit_id NUMBER,
User_name VARCHAR2(20),
Now_time DATE,
Terminal_name VARCHAR2(10),
Table_name VARCHAR2(10),
Action_name VARCHAR2(10),
Emp_id NUMBER(4));

CREATE TABLE audit_table_val(
Audit_id NUMBER,
Column_name VARCHAR2(10),
Old_val NUMBER(7,2),
New_val NUMBER(7,2));

CREATE SEQUENCE audit_seq
START WITH 1000
INCREMENT BY 1
NOMAXVALUE
NOCYCLE NOCACHE;

CREATE OR REPLACE TRIGGER audit_emp
AFTER INSERT OR UPDATE OR DELETE ON emp
FOR EACH ROW
DECLARE
Time_now DATE;
Terminal CHAR(10);
BEGIN
Time_now:=sysdate;
Terminal:=USERENV(‘TERMINAL’);
IF INSERTING THEN
INSERT INTO audit_table
VALUES(audit_seq.NEXTVAL, user, time_now,
terminal, ‘EMP’, ‘INSERT’, :new.empno);
ELSIF DELETING THEN
INSERT INTO audit_table
VALUES(audit_seq.NEXTVAL, user, time_now,
terminal, ‘EMP’, ‘DELETE’, :old.empno);
ELSE
INSERT INTO audit_table
VALUES(audit_seq.NEXTVAL, user, time_now,
terminal, ‘EMP’, ‘UPDATE’, :old.empno);
IF UPDATING(‘SAL’) THEN
INSERT INTO audit_table_val
VALUES(audit_seq.CURRVAL, ‘SAL’, :old.sal, :new.sal);
ELSE UPDATING(‘DEPTNO’)
INSERT INTO audit_table_val
VALUES(audit_seq.CURRVAL, ‘DEPTNO’, :old.deptno, :new.deptno);
END IF;
END IF;
END;

l 增强数据的完整性管理;
例:修改DEPT表的DEPTNO列时,同时把EMP表中相应的DEPTNO也作相应的修改;

CREATE SEQUENCE update_sequence
INCREMENT BY 1
START WITH 1000
MAXVALUE 5000 CYCLE;

ALTER TABLE emp
ADD update_id NUMBER;

CREATE OR REPLACE PACKAGE integritypackage AS
Updateseq NUMBER;
END integritypackage;

CREATE OR REPLACE PACKAGE BODY integritypackage AS
END integritypackage;

CREATE OR REPLACE TRIGGER dept_cascade1
BEFORE UPDATE OF deptno ON dept
DECLARE
Dummy NUMBER;
BEGIN
SELECT update_sequence.NEXTVAL INTO dummy FROM dual;
Integritypackage.updateseq:=dummy;
END;

CREATE OR REPLACE TRIGGER dept_cascade2
AFTER DELETE OR UPDATE OF deptno ON dept
FOR EACH ROW
BEGIN
IF UPDATING THEN
UPDATE emp SET deptno=:new.deptno,
update_id=integritypackage.updateseq
WHERE emp.deptno=:old.deptno AND update_id IS NULL;
END IF;
IF DELETING THEN
DELETE FROM emp
WHERE emp.deptno=:old.deptno;
END IF;
END;

CREATE OR REPLACE TRIGGER dept_cascade3
AFTER UPDATE OF deptno ON dept
BEGIN
UPDATE emp SET update_id=NULL
WHERE update_id=integritypackage.updateseq;
END;

SELECT * FROM EMP ORDER BY DEPTNO;
UPDATE dept SET deptno=25 WHERE deptno=20;

l 帮助实现安全控制;
例:保证对EMP表的修改仅在工作日的工作时间;

CREATE TABLE company_holidays(day DATE);

INSERT INTO company_holidays
VALUES(sysdate);
INSERT INTO company_holidays
VALUES(TO_DATE(‘21-10月-01’, ‘DD-MON-YY’));

CREATE OR REPLACE TRIGGER emp_permit_change
BEFORE INSERT OR DELETE OR UPDATE ON emp
DECLARE
Dummy NUMBER;
Not_on_weekends EXCEPTION;
Not_on_holidays EXCEPTION;
Not_working_hours EXCEPTION;
BEGIN
/* check for weekends */
IF TO_CHAR(SYSDATE, ‘DAY’) IN (‘星期六’, ‘星期日’) THEN
RAISE not_on_weekends;
END IF;
/* check for company holidays */
SELECT COUNT(*) INTO dummy FROM company_holidays
WHERE TRUNC(day)=TRUNC(SYSDATE);
IF dummy >0 THEN
RAISE not_on_holidays;
END IF;
/* check for work hours(8:00 AM to 18:00 PM */
IF (TO_CHAR(SYSDATE,’HH24’)<8 OR TO_CHAR(SYSDATE, ‘HH24’)>18) THEN
RAISE not_working_hours;
END IF;
EXCEPTION
WHEN not_on_weekends THEN
RAISE_APPLICATION_ERROR(-20324,
‘May not change employee table during the weekends’);
WHEN not_on_holidays THEN
RAISE_APPLICATION_ERROR(-20325,
‘May not change employee table during a holiday’);
WHEN not_working_hours THEN
RAISE_APPLICATION_ERROR(-20326,
‘May not change employee table during no_working hours’);
END;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值