---参考:《Oracle 11g SQL和 PLSQL从入门到精通》
一:DML触发器
1.1语句级别触发器
1.2 行级触发器
1.3 组合触发器
1.4 DML触发器开发示例
二:INSTEND OF触发器
三:事件触发器
四:维护触发器
一 DML触发器
1.1 语句级触发器
语句级触发器是指当执行DML语句时被隐含执行的触发器。
如果执行了相关的DML语句,那么会执行该触发器的相应代码。
语法如下:
CREATE [OR REPLACE] TRIGGER trigger_name {BEFORE | AFTER} event1 [OR event2 OR event3]ON table_namePL/SQL block;
1.1.1建立BEFORE语句触发器
BEFORE语句触发器是指在执行DML语句之前被触发的触发器。
下面禁止在周六、周日改变EMP表数据为例。
select to_char(sysdate,'DY') FROM dual;
CREATE OR REPLACE TRIGGER tr_sec_emp BEFORE INSERT OR UPDATE OR DELETE ON empBEGIN IF to_char(sysdate, 'DY', 'nls_date_language=AMERICAN') IN ('SAT', 'SUN') THEN raise_application_error(-20001, 'Employee information cannot be changed on a rest day!'); END IF;END;/
SQL> UPDATE emp SET sal=sal*1.1 WHERE deptno=10;UPDATE emp SET sal=sal*1.1 WHERE deptno=10 *ERROR at line 1:ORA-20001: Employee information cannot be changed on a rest day!ORA-06512: at "SCOTT.TR_SEC_EMP", line 3ORA-04088: error during execution of trigger 'SCOTT.TR_SEC_EMP'SQL> rollback;
1.1.2建立AFTER语句触发器
AFTER语句触发器是指在执行DML语句之后被触发的触发器。
下面以审计在EMP表上执行UPADTE语句的客户主机名、语句以及执行时间为例。
CREATE TABLE aud_upd_table(host VARCHAR2(30),statement VARCHAR2(100),exectime DATE);
CREATE OR REPLACE TRIGGER tr_upd_emp AFTER UPDATE ON empDECLARE sql_txt ora_name_list_t; v_stmt VARCHAR2(100); n BINARY_INTEGER;BEGIN n := ora_sql_txt(sql_txt); FOR i IN 1 .. n LOOP v_stmt := v_stmt || sql_txt(i); END LOOP; INSERT INTO aud_upd_table VALUES (sys_context('userenv', 'host'), v_stmt, SYSDATE);END;/
SQL> UPDATE emp SET sal=2000.00 WHERE empno=7369;UPDATE emp SET sal=2000.00 WHERE empno=7369 *ERROR at line 1:ORA-06502: PL/SQL: numeric or value errorORA-06512: at "SCOTT.TR_UPD_EMP", line 7ORA-04088: error during execution of trigger 'SCOTT.TR_UPD_EMP'
创建错误,提示FOR i IN 1..N LOOP错误,提示n不是数值。
1.1.3 使用条件谓词
当DML触发器包含多个触发事件(INSERT、UPDATE、DELETE)时,为了区分具体的触发事件,可以使用以下条件谓词:
INSERTING:当触发事件是INSERT语句时,返回TRUE,否则返回FALSE。
UPDATING:当触发事件是UPDATE语句时,返回TRUE,否则返回FALSE。
DELETING:当触发事件是DELETE语句时,返回TRUE,否则返回FALSE。
下面以防止周六、周日在EMP表上执行DML,根据不同谓词显示不同错误信息,并建立触发器tr_sec_emp为例,说明使用条件谓词的方法。
CREATE OR REPLACE TRIGGER tr_sec_emp BEFORE INSERT OR UPDATE OR DELETE ON empBEGIN IF to_char(sysdate, 'DY', 'nls_date_language=AMERICAN') in ('SAT', 'SUN') THEN CASE WHEN INSERTING THEN raise_application_error(-20001, ''); WHEN UPDATING THEN raise_application_error(-20002, ''); WHEN DELETING THEN raise_application_error(-20003, ''); END CASE; END IF;END;/
SQL> DELETE FROM emp WHERE empno=7788;DELETE FROM emp WHERE empno=7788 *ERROR at line 1:ORA-20003: Do not delete data on a rest day!ORA-06512: at "SCOTT.TR_SEC_EMP", line 9ORA-04088: error during execution of trigger 'SCOTT.TR_SEC_EMP'SQL> update emp set sal=sal*1.1 where empno=7788;update emp set sal=sal*1.1 where empno=7788 *ERROR at line 1:ORA-20002: Do not update data on a rest day!ORA-06512: at "SCOTT.TR_SEC_EMP", line 7ORA-04088: error during execution of trigger 'SCOTT.TR_SEC_EMP'
1.2 行触发器
行触发器是指当执行DML语句时,每作用一行被触发一次的触发器。
为了审计表数据的变化,可以使用行触发器。
语法如下:
CREATE [OR REPLACE] TRIGGER trigger_name {BEFORE | AFTER} event1 [OR event2 OR event3]ON table_name [REFERENCING OLD AS old | NEW AS new]FOR EACH ROW [WHEN condition]PL/SQL block;
1.2.1 建立BEFORE行触发器
在处理行之前被触发的触发器
下面以确保雇员工资不能低于其原有工资。
CREATE OR REPLACE TRIGGER tr_emp_salBEFORE UPDATE OF sal ON emp FOR EACH ROWBEGIN IF :new.sal<:old.sal>THEN raise_application_error(-20010,'Wages can only go up, not down!'); END IF;END;/
SQL> UPDATE emp SET sal=500 WHERE empno=7369;UPDATE emp SET sal=500 WHERE empno=7369 *ERROR at line 1:ORA-20010: Wages can only go up, not down!ORA-06512: at "SCOTT.TR_EMP_SAL", line 3ORA-04088: error during execution of trigger 'SCOTT.TR_EMP_SAL'
1.2.2建立AFTER行触发器
AFTER行触发器是指在处理行之后被触发的触发器。
下面以审计雇员工资变化,并建立行触发器tr_sal_change为例。
CREATE TABLE audit_emp_change(name varchar2(10),oldsal NUMBER(6,2),newsal NUMBER(6,2),time DATE);
CREATE OR REPLACE TRIGGER tr_sal_change AFTER UPDATE OF sal ON emp FOR EACH ROWDECLARE v_temp INT;BEGIN SELECT count(*) INTO v_temp FROM audit_emp_change WHERE name = :old.ename; IF v_temp = 0 THEN INSERT INTO audit_emp_change VALUES (:old.ename, :old.sal, :new.sal, SYSDATE); ELSE UPDATE audit_emp_change SET oldsal = :old.sal, newsal = :new.sal, time = SYSDATE WHERE name = :old.ename; END IF;END;/
SQL> UPDATE emp SET sal=sal*1.1 WHERE deptno=10;3 rows updated.SQL> select * from audit_emp_change;NAME OLDSAL NEWSAL TIME---------- ---------- ---------- ---------CLARK 2964.5 3260.95 09-JUN-20KING 6050 6655 09-JUN-20MILLER 1573 1730.3 09-JUN-20SQL> rollback;Rollback complete.SQL> select * from audit_emp_change;no rows selected
1.2.3限制行触发器
需要使用WHEN子句对触发条件加以限制。以SALESMAN的雇员工资变化为例。
CREATE OR REPLACE TRIGGER tr_sal_change AFTER UPDATE OF sal ON emp FOR EACH ROW WHEN (old.job='MANAGER')DECLARE v_temp INT;BEGIN SELECT count(*) INTO v_temp FROM audit_emp_change WHERE name = :old.ename; IF v_temp = 0 THEN INSERT INTO audit_emp_change VALUES (:old.ename, :old.sal, :new.sal, SYSDATE); ELSE UPDATE audit_emp_change SET oldsal = :old.sal, newsal = :new.sal, time = SYSDATE WHERE name = :old.ename; END IF;END;/
SQL> select job,deptno from emp;JOB DEPTNO--------- ----------CLERK 20MANAGER 20MANAGER 10PRESIDENT 10CLERK 20ANALYST 20CLERK 107 rows selected.SQL> UPDATE emp SET sal=sal*1.1 WHERE deptno=10;3 rows updated.SQL> select * from audit_emp_change;NAME OLDSAL NEWSAL TIME---------- ---------- ---------- ---------CLARK 2964.5 3260.95 09-JUN-20
1.3 组合触发器
组合触发器(Compound Trigger)是Oracle Database 11g的新特性。
无论编写语句触发器还是行级触发器,它们只具有一种触发时机,而组合触发器往往具有多种触发时机。
示例:
CREATE OR REPLACE TRIGGER tr_update_sal FOR UPDATE OF sal ON emp COMPOUND TRIGGER msg1 VARCHAR2(50) := 'Salary range must be 1000 to 5000!'; msg2 VARCHAR2(50) := 'Cannot be updated on a rest day!'; BEFORE STATEMENT ISBEGIN IF to_char(sysdate, 'DY', 'nls_date_language=AMERICAN') IN ('SAT', 'SUN') THEN raise_application_error(-20000, msg2); END IF;END BEFORE STATEMENT; AFTER EACH ROW IS BEGIN IF :new.sal NOT BETWEEN 1000 AND 5000 THEN raise_application_error(-20001, msg1); END IF;END AFTER EACH ROW;END;/
SQL> UPDATE emp SET sal=900 WHERE empno=7369; UPDATE emp SET sal=900 WHERE empno=7369 *ERROR at line 1:ORA-20001: Salary range must be 1000 to 5000!ORA-06512: at "SCOTT.TR_UPDATE_SAL", line 14ORA-04088: error during execution of trigger 'SCOTT.TR_UPDATE_SAL'
1.4 DML触发器开发示例
DML触发器可用于实现数据安全保护、数据审计、数据完整性、参照完整性、数据复制等功能。
1.4.1控制数据安全
下面限制用户在正常工作时间(9:00-17:00)改变EMP表数据为例:
CREATE OR REPLACE TRIGGER tr_emp_timeBEFORE INSERT OR UPDATE OR DELETE ON empBEGIN IF to_char(SYSDATE,'HH24') NOT BETWEEN '11' AND '17' THEN raise_application_error(-20101,'Non working time!'); END IF;END;/
SQL> UPDATE emp SET sal=3200 WHERE empno=7369;UPDATE emp SET sal=3200 WHERE empno=7369 *ERROR at line 1:ORA-20101: Non working time!ORA-06512: at "SCOTT.TR_EMP_TIME", line 3ORA-04088: error during execution of trigger 'SCOTT.TR_EMP_TIME'
1.4.2 实现数据审计
审计用于监视非法和可疑的数据库活动,但数据库审计只能用于监视用户操作,而不能记录数据变化。
为了审计表数据的变化,可以使用DML行触发器。
下面以审计删除时间,以及被删除的雇员名为例。
SQL>CREATE TABLE audit_delete_emp(name VARCHAR2(10),time DATE);CREATE OR REPLACE TRIGGER tr_delete_empAFTER DELETE ON emp FOR EACH ROWBEGIN INSERT INTO audit_delete_emp VALUES(:old.ename,SYSDATE);END;/
SQL> DELETE FROM emp WHERE empno=7369;1 row deleted.SQL> SELECT * FROM audit_delete_emp;NAME TIME---------- ---------SMITH 10-JUN-20
1.4.3实现数据完整性
要求雇员新工资不能低于原工资,并且不能超过原工资20%。
SQL>CREATE OR REPLACE TRIGGER tr_check_salBEFORE UPDATE OF sal ON emp FOR EACH ROWWHEN (new.salOR BEGIN raise_application_error(-20931,'Wages can only be increased but not decreased, and the increase cannot exceed 20%');END;/
SQL> UPDATE emp SET sal=sal*1.25 WHERE empno=7369;UPDATE emp SET sal=sal*1.25 WHERE empno=7369 *ERROR at line 1:ORA-20931: Wages can only be increased but not decreased, and the increase cannot exceed 20%ORA-06512: at "SCOTT.TR_CHECK_SAL", line 2ORA-04088: error during execution of trigger 'SCOTT.TR_CHECK_SAL'
1.4.4实现参照完整性
参照完整性是指在两张表之间具有主从关系(也即主外键关系)。当删除主表数据时,需要首先删除从表的相关数据;
当更新主表主键列时,需要首先更新从表相关数据。
为了实现级联删除,可以在定义外部键约束时指定ON DELETE CASCADE关键字。
但使用约束却不能实现级联更新。
为了实现级联更新,需要使用触发器。
示例:
CREATE OR REPLACE TRIGGER tr_update_cascadeAFTER UPDATE OF deptno ON dept FOR EACH ROWBEGIN UPDATE emp SET deptno=:new.deptno WHERE deptno=:old.deptno;END;/
UPDATE dept SET deptno=50 where deptno=10;SELECT ename FROM emp WHERE deptno=50;---ENAMECLARKKINGMILLER
二 INSTEAD OF触发器
在简单视图上往往可以执行INSERT、UPDATE和DELETE操作,但在复杂视图上执行DML操作时有限制的。
例如视图子查询包含有集合操作符、分组函数、DISTINCT关键字或者连接查询,那么将禁止在该视图上执行DML操作。
为了在这些复杂视图上执行DML操作,需要建立INSTEAD-OF触发器。
INSTEAD-OF触发器只适用于视图。
SQL> CREATE OR REPLACE VIEW dept_emp AS SELECT a.deptno,a.dname,b.empno,b.ename FROM dept a,emp b WHERE a.deptno=b.deptno;SQL> select * from dept_emp;SQL> INSERT INTO dept_emp VALUES(50,'ADMIN',1223,'MARY');INSERT INTO dept_emp VALUES(50,'ADMIN',1223,'MARY')*ERROR at line 1:ORA-01779: cannot modify a column which maps to a non key-preserved table
http://www.itpub.net/thread-225291-1-1.html
1)view同一时刻只能对一个表进行操作。
2)“cannot modify a column which maps to a non key-preserved table”意思就是:
当对一个view进行delete、insert、update时,被(涉及到的)view列所映射table列(或列的组合)必须是有主健约束的,目的是要保证在同一时刻只能针对一条记录操作。
3)如果要实现“用一条SQL语句,想通过视图同时往三个表中插入数据”,可以在触发器里代码实现。
CREATE OR REPLACE TRIGGER tr_instead_of_dept_empINSTEAD OF INSERT ON dept_emp FOR EACH ROWDECLAREv_temp INT;BEGIN SELECT count(*) INTO v_temp FROM dept WHERE deptno=:new.deptno; IF v_temp=0 THEN INSERT INTO dept(deptno,dname) VALUES(:new.deptno,:new.dname); END IF; SELECT count(*) INTO v_temp FROM emp WHERE empno=:new.empno; IF v_temp=0 THEN INSERT INTO emp(empno,ename,deptno) VALUES(:new.empno,:new.ename,:new.deptno); END IF;END;/
SQL> INSERT INTO dept_emp VALUES(50,'ADMIN',1223,'MARY');SQL> select dname from dept where deptno=50;---ADMINSQL> SELECT ename FROM emp WHERE empno=1223;---MARY
三 事件触发器
指基于Oracle系统事件或者客户事件所建立的触发器,通过使用事件触发器,提供了跟踪系统或数据库变化的机制。
当建立事件触发器时,需要使用各种事件属性函数。
3.1事件属性函数
1.ora_client_ip_address:该属性用于返回客户端IP地址。2.ora_database_name:该属性用于返回数据名。3.ora_des_encrypted_password:该属性用于返回当建立或修改用户时DES加密后的用户口令。4.ora_dict_obj_name:该属性用于返回DDL操作所对应对象的名称。5.ora_dict_obj_name_list:该属性用于返回特定事件所修改对象的对象名个数。6.ora_dict_obj_owner_list:该属性用于返回DDL操作所对应对象的所有者列表个数。7.ora_dict_obj_type:该属性用于返回特定事件所修改对象的所有者列表个数。8.ora_grantee:该属性用于返回被授权者个数。9.ora_instance_num:该属性用于返回实例编号。10.ora_is_alter_column:该属性用于检测特定列是否被修改。11.ora_is_creating_nested_table:该属性用于检测是否正在建立嵌套表。12.ora_is_drop_column:该属性用于检测特定列是否被删除。13.ora_is_servererror:该属性用于检测是否返回特定Oracle错误。14.ora_login_user:该属性用于返回登录用户名。15.ora_dict_obj_owner:该属性用于返回DDL操作所对应对象的所有者名称。16.ora_partition_pos:该属性用于确定SQL语句文本中插入PARTITION子句的位置。17.ora_privilege_list:该属性用于返回被授予或者被收回权限的个数。18.ora_revokee:该属性用于返回被收回权限的用户个数。19.ora_server_error:该属性用于返回在错误堆栈中特定错误位置所对应的错误号。20.ora_server_error_depth:该属性用于返回在错误堆栈中错误消息的总数。21.ora_server_error_msg:该属性用于返回在错误堆栈中特定错误位置的错误消息。22.ora_server_error_num_params:该属性返回在错误堆栈中特定错误位置特定参数号所对应的字符串替代值。23.ora_server_error_param:该属性用于返回在错误堆栈中特定错误位置特定参数号所对应的字符串替代值。24.ora_sql_txt:该属性用于返回触发器语句的SQL文本元素个数。25.ora_sysevent:用于返回触发触发器的系统事件名。26.ora_with_grant_option:该属性用于确定授权是否带有WITH GRANT OPTOIN选项。27.space_error_info:该属性用于确定错误是否与out-of-space相关。
3.2系统事件触发器
是由特定系统事件所触发的触发器。
包括STARTUP、SHUTDOWN、DB_ROLE_CHANGE、SERVERERROR四个事件。
注意:系统事件触发器只能由SYS用户建立,并且SHUTDOWN ABORT命令不会触发SHUTDOWN事件。
示例:
SQL> conn / as sysdbaSQL> create table event_table(event varchar2(30),time date);
SQL>CREATE OR REPLACE TRIGGER tr_startupAFTER STARTUP ON DATEBASEBEGIN INSERT INTO event_table VALUES(ora_sysevent,SYSDATE);END;/ORA-30506: system triggers cannot be based on tables or views创建触发器报错ORA-30506SQL> SHUDOWNSQL> STARTUPSQL> SELECT * FROM event_table;
3.3 客户事件触发器
指基于客户事件所建立的触发器,客户事件是指与用户登录、用户注销、DDL以及DCL相关的事件。
客户事件触发器只能由SYS用户建立。
3.3.1 建立登录触发器
conn / as sysdbaCREATE TABLE aud_logon_tab(username varchar2(20),time DATE,addr VARCHAR2(20));
CREATE OR REPLACE TRIGGER tr_logonAFTER LOGON ON DATABASEBEGIN INSERT INTO aud_logon_tab VALUES(ora_login_user,SYSDATE,ora_client_ip_address);END;/
conn cjc/cjcconn scott/tigerSELECT * FROM sys.aud_logon_tab;USERNAME TIME ADDR-------------------- --------- --------------------CJC 10-JUN-20SCOTT 10-JUN-20SYS 10-JUN-20
3.3.2 建立DDL触发器
为了记录系统所发生的DDL事件,可以建立DDL触发器。
SQL> conn / as sysdbaSQL> CREATE TABLE aud_ddl_tab(event VARCHAR2(20),username VARCHAR2(10),owner VARCHAR2(10),objname VARCHAR2(20),objtype VARCHAR2(10),time DATE);SQL> grant select on aud_ddl_tab to scott;
SQL>CREATE OR REPLACE TRIGGER tr_ddlAFTER DDL ON scott.schemaBEGIN INSERT INTOaud_ddl_tab VALUES(ora_sysevent,ora_login_user,ora_dict_obj_owner,ora_dict_obj_name,ora_dict_obj_type,SYSDATE);END;/
SQL> conn scott/tigerSQL> CREATE TABLE temp(cola INT);SQL> SELECT * FROM sys.aud_ddl_tab;EVENT USERNAMEOWNER OBJNAMEOBJTYPE TIME-------------------- ---------- ---------- -------------------- ---------- ---------CREATE SCOTTSCOTT TEMP TABLE 11-JUN-20
四 维护触发器
4.1 显示触发器信息
通过user_triggers查看当前用户触发器信息。
SQL> set long 800SQL> set pagesize 400SQL> select trigger_body FROM user_triggers WHERE trigger_name='TR_CHECK_SAL';TRIGGER_BODY--------------------------------------------------------------------------------BEGIN raise_application_error(-20931,'Wages can only be increased but not decreased, and the increase cannot exceed 20%');END;
SQL> select dbms_metadata.get_ddl('TRIGGER','TR_CHECK_SAL','SCOTT') from dual;DBMS_METADATA.GET_DDL('TRIGGER','TR_CHECK_SAL','SCOTT')-------------------------------------------------------------------------------- CREATE OR REPLACE TRIGGER "SCOTT"."TR_CHECK_SAL"BEFORE UPDATE OF sal ON emp FOR EACH ROW WHEN (new.salOR raise_application_error(-20931,'Wages can only be increased but not decreased, and the increase cannot exceed 20%');END;ALTER TRIGGER "SCOTT"."TR_CHECK_SAL" ENABLE
4.2禁止触发器
ALTER TRIGGER "SCOTT"."TR_CHECK_SAL" DISABLE;select STATUS FROM user_triggers WHERE trigger_name='TR_CHECK_SAL';
如果禁用表emp的所有触发器,可以使用:
ALTER TABLE SCOTT.EMP DISABLE ALL TRIGGERS;
4.3激活触发器
ALTER TRIGGER "SCOTT"."TR_CHECK_SAL" ENABLE;
如果激活表emp的所有触发器,可以使用:
ALTER TABLE SCOTT.EMP ENABLE ALL TRIGGERS;
4.4重新编译触发器
当使用ALTER TABLE命令修改表结构时,会使相关触发器转变为INVALID状态,为了使用这些触发器,需要重新编译。
ALTER TRIGGER tr_upd_emp COMPILE;
4.5删除触发器
DROP TRIGGER tr_check_sal;
更多数据库相关学习资料,可以查看我的ITPUB博客,网名chenoracle:
http://blog.itpub.net/29785807/