DML_DDL_触发器
Oracle触发器1-介绍
Oracle官方参考:PL/SQL Language Referenc->9 PL/SQL Trigger
Reasons to Use Trigger:
■ Automatically generate calculated column values
■ Log events
■ Gather statistics on table access
■ Modify table data when DML statements are issued against views
■ Enforce referential integrity when child and parent tables are on different nodes of a distributed database
■ Publish information about database events, user events, and SQL statements to subscribing applications
■ Prevent DML operations on a table after regular business hours
■ Prevent invalid transactions
■ Enforce complex business or referential integrity rules that you cannot define with constraints
本人E比较差,防止误导别人,也就不一一解释。
触发器在数据库里以独立的对象存储,它与存储过程和函数不同的是,存储过程与函数需要用户显示调用才执行,而触发器是由一个事件来启动运行。即触发器是当某个事件发生时自动地隐式运行。并且,触发器不能像存储过程一样接收参数。ORACLE事件指的是对数据库的表进行的INSERT、UPDATE及DELETE操作或对视图进行类似的操作。ORACLE将触发器的功能扩展到了触发ORACLE,如数据库的启动与关闭等。所以触发器常用来完成由数据库的完整性约束难以完成的复杂业务规则的约束,或用来监视对数据库的各种操作,实现审计的功能。
DML触发器
ORACLE可以在DML语句进行触发,可以在DML操作前或操作后进行触发,并且可以对每个行或语句操作上进行触发。
DDL触发器
创建DDL触发器语法和DML触发器很类似,只是触发事件不同(create table,alter index,drop trigger等),这种触发器不是作用于某个表的。
替代触发器
由于在ORACLE里,不能直接对由两个以上的表建立的视图进行操作。所以给出了替代触发器。
数据库事件触发器
ORACLE 提供了第三种类型的触发器,数据库事件触发器也叫系统触发器。它可以在ORACLE数据库系统的事件中进行触发,如ORACLE系统的启动与关闭等。
触发器组成部分
1、 触发器名称
2、 触发语句
3、 触发器限制
4、 触发操作
Trigger Design Guidelines:
■ Use triggers to ensure that whenever a specific event occurs, any necessary actions are done (regardless of which user or application issues the triggering statement).
For example, use a trigger to ensure that whenever anyone updates a table, its log file is updated.
■ Do not create triggers that duplicate database features.
For example, do not create a trigger to reject invalid data if you can do the same with constraints.
■ Do not create triggers that depend on the order in which a SQL statement processes rows (which can vary).
For example, do not assign a value to a global package variable in a row trigger if the current value of the variable depends on the row being processed by the row trigger. If a trigger updates global package variables, initialize those variables in a BEFORE statement trigger.
■ Use BEFORE row triggers to modify the row before writing the row data to disk.
■ Use AFTER row triggers to obtain the row ID and use it in operations.An AFTER row trigger fires when the triggering statement results in ORA-2292.
■ If the triggering statement of a BEFORE statement trigger is an UPDATE or DELETEstatement that conflicts with an UPDATE statement that is running, then the
database does a transparent ROLLBACK to SAVEPOINT and restarts the triggering statement. The database can do this many times before the triggering statement
completes successfully. Each time the database restarts the triggering statement, the trigger fires. The ROLLBACK to SAVEPOINT does not undo changes to package
variables that the trigger references. To detect this situation, include a counter variable in the package.
■ Do not create recursive triggers.
For example, do not create an AFTER UPDATE trigger that issues an UPDATE statement on the table on which the trigger is defined.
The trigger fires recursively until it runs out of memory
■ Use DATABASE triggers judiciously. They fire every time any database user initiates a triggering event.
■ If a trigger runs the following statement, the statement returns the owner of the trigger, not the user who is updating the table:
SELECT Username FROM USER_USERS;
■ Only committed triggers fire.
A trigger is committed, implicitly, after the CREATE TRIGGER statement that creates it succeeds.
Therefore, the following statement cannot fire the trigger that it creates:
CREATE OR REPLACE TRIGGER my_trigger
AFTER CREATE ON DATABASE
BEGIN
NULL;
END;
/
■ To allow the modular installation of applications that have triggers on the same tables, create multiple triggers of the same type, rather than a single trigger that
runs a sequence of operations.Each trigger sees the changes made by the previously fired triggers. Each trigger can see OLD and NEW values.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
Oracle触发器2-DML触发器
DML触发器是最常见的触发器类型,开发人员用的比较多;而其他类型的触发器主要是用于数据库管理或者审计,DBA用的比较多。
1、DML触发器简介:
BEFORE 触发器
这种触发器是在某个操作发生之前触发的,比如before insert就是在插入操作之前触发。
AFTER 触发器
这种触发器是在某个操作发生之后触发的,比如after update就是在插入操作之前触发。
语句级别触发器
这种触发器是由整个SQL语句触发的。这个SQL语句可能操作数据库表的一条或者多条数据。
行级别触发器
这种触发器针对的是SQL语句执行过程中操作的每一行记录。假设books表中有1000行记录。下面的update语句就会修改1000行记录:
update books set title = upper(title);
如果我在books表上定义了一个行级别的更新触发器,这个触发器就会被触发1000次。
伪记录 NEW
这是一个被叫做NEW的数据结构,看起来和PL/SQL中的记录非常相似。只有在更新操作和插入操作的DML触发器中才能使用这个伪记录;这个记录包含的是被操作的行修改之后的值。
伪记录 OLD
这是一个被叫做OLD的数据结构,看起来和PL/SQL中的记录非常相似。只有在更新操作和插入操作的DML触发器中才能使用这个伪记录;这个记录包含的是被操作的行修改之前的值。
WHEN 子句
DML触发器用这个子句来确定是否应该执行触发器的代码,我们可以用它来避免不必要的执行。
有关事务
DML触发器会参与到触发他们的事务中。
如果触发器抛出了异常,这部分事务会回滚(rollback)。
如果触发器本身也执行了DML语句(比如向日志表中插入一行数据),这个DML同时也会成为主体事务的一部分。
不能在DML触发器里执行commit或者rollback语句。
2、创建DML触发器
1 CREATE [OR REPLACE] TRIGGER trigger_name --指定一个触发器名字, or replace 可选
2 {BEFORE | AFTER} --指定触发器时机是在语句执行之前或者之后。
3 {INSERT | DELETE | UPDATE | UPDATE OF column_list } ON table_name --指定触发器应用的DML类型组合:插入、更新或者删除操作。
4 [FOR EACH ROW] --如果指定了for each row 则语句处理的每一行记录都会激活触发器。
5 [WHEN (...)] --通过这个可选的when子句,可以避免不必要的执行
6 [DECLARE ... ]
7 BEGIN
8 ...executable statements... --执行体
9 [EXCEPTION ... ] --可选异常处理部分
10 END [trigger_name];
Examples:
-- an after statement level trigger
CREATE OR REPLACE TRIGGER statement_trigger
AFTER INSERT ON to_table
BEGIN
DBMS_OUTPUT.PUT_LINE('After Insert Statement Level');
END;
/
/*-- an after row level trigger */
CREATE OR REPLACE TRIGGER row_trigger
AFTER INSERT ON to_table
FOR EACH ROW
BEGIN
DBMS_OUTPUT.PUT_LINE('After Insert Row Level');
END;
/
-- a before statement level trigger
CREATE OR REPLACE TRIGGER before_statement_trigger
BEFORE INSERT ON to_table
BEGIN
DBMS_OUTPUT.PUT_LINE('Before Insert Statement Level');
END;
/
-- a before row level trigger
CREATE OR REPLACE TRIGGER before_row_trigger
BEFORE INSERT ON to_table
FOR EACH ROW
BEGIN
DBMS_OUTPUT.PUT_LINE('Before Insert Row Level');
END;
/
-- after insert statement
CREATE OR REPLACE TRIGGER after_insert_statement
AFTER INSERT ON to_table
BEGIN
DBMS_OUTPUT.PUT_LINE('After Insert Statement');
END;
/
-- after update statement
CREATE OR REPLACE TRIGGER after_update_statement
AFTER UPDATE ON to_table
BEGIN
DBMS_OUTPUT.PUT_LINE('After Update Statement');
END;
/
-- after delete statement
CREATE OR REPLACE TRIGGER after_delete_statement
AFTER DELETE ON to_table
BEGIN
DBMS_OUTPUT.PUT_LINE('After Delete Statement');
END;
/
2.1、使用WHEN子句
例如使用WHEN子句确保只有把薪水修改成不同的值时触发器代码才会执行:
CREATE OR REPLACE TRIGGER check_raise
AFTER UPDATE OF salary
ON employees
FOR EACH ROW
WHEN ( (old.salary != new.salary)
OR (old.salary IS NULL AND new.salary IS NOT NULL)
OR (old.salary IS NOT NULL AND new.salary IS NULL))
BEGIN
NULL;
END;
/
WHEN子句使用注意事项:
a.要把整个判断逻辑表达式括起来()
b.不要在OLD和NEW之前加上”:”
c.使用WHEN子句时只能使用SQL内置函数;
2.2、使用NEW和OLD伪记录
CREATE OR REPLACE TRIGGER bef_ins_ceo_comp
BEFORE INSERT
ON ceo_compensation
FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO ceo_comp_history
VALUES (
:new.name
, :old.compensation
, :new.compensation
, 'AFTER INSERT'
, SYSDATE
);
COMMIT;
END;
/
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
Oracle触发器3-DDL触发器
DDL触发器,当执行DDL语句时会被触发。按照作用范围,分为schema triggers,database triggers。schema triggers作用在一个用户上,database triggers作用在整个数据库所有用户上。
创建DDL触发器
要创建一个DDL触发器,语法如下:
1 CREATE [OR REPLACE] TRIGGER trigger name --创建一个触发器并制定名称,or replace是可选项
2 {BEFORE | AFTER } { DDL event} ON {DATABASE | SCHEMA} --指定触发器是在DDL事件之前、之后触发。范围是on database、on schema
3 [WHEN (...)] --可选的WHEN子句,使用逻辑判断来避免触发器无意义的执行
4 DECLARE --触发器具体内容4-7
5 Variable declarations
6 BEGIN
7 ...some code...
8 END;
Examples:
SQL> CREATE OR REPLACE TRIGGER hr.testtrigger
2 AFTER CREATE ON SCHEMA -- on schema 作用范围只是在hr用户下create table等触发,其他用户则不会。若是on database则其他用户create table时会触发该触发器
3 BEGIN
4 -- 以下使用的是事件属性
5 DBMS_OUTPUT.PUT_LINE('I believe you have created a ' ||
6 ORA_DICT_OBJ_TYPE || ' called ' ||
7 ORA_DICT_OBJ_NAME);
8 END;
9 /
Trigger created.
可用事件
可用的DDL事件
DDL事件 触发时机
ALTER 对数据库中的任何一个对象使用SQL的ALTER命令时触发
ANALYZE 对数据库中的任何一个对象使用SQL的ANALYZE命令时触发
ASSOCIATE STATISTICS 统计数据关联到数据库对象时触发
AUDIT 通过SQL的AUDIT命令打开审计时触发
COMMENT 对数据库对象做注释时触发
CREATE 通过SQL的CREATE命令创建数据库对象时触发
DDL 列表中所用的事件都会触发
DISASSOCIATE STATISTICS 去掉统计数据和数据库对象的关联时触发
DROP 通过SQL的DROP命令删除数据库对象时触发
GRANT 通过SQL的GRANT命令赋权时触发
NOAUDIT 通过SQL的NOAUDIT关闭审计时触发
RENAME 通过SQL的RENAME命令对对象重命名时触发
REVOKE 通过SQL的REVOKE语句撤销授权时触发
TRUNCATE 通过SQL的TRUNCATE语句截断表时触发
可用属性
Oracle 提供了一系列的函数用来提供关于什么触发了DDL触发器以及触发器的状态灯信息。上面那个触发器的例子就使用了属性。
DDL触发器事件以及属性函数
函数名 返回值
ORA_CLIENT_IP_ADDRESS 客户端IP地址
ORA_DATABASE_NAME 数据库名称
ORA_DES_ENCRYPTED_PASSWORD 当前用户的DES算法加密后的密码
ORA_DICT_OBJ_NAME 触发DDL的数据库对象名称
ORA_DICT_OBJ_NAME_LIST 受影响的对象数量和名称列表
ORA_DICT_OBJ_OWNER 触发DDL的数据库对象属主
ORA_DICT_OBJ_OWNER_LIST 受影响的对象数量和名称列表
ORA_DICT_OBJ_TYPE 触发DDL的数据库对象类型
ORA_GRANTEE 被授权人数量
ORA_INSTANCE_NUM 数据库实例数量
ORA_IS_ALTER_COLUMN 如果操作的参数column_name指定的列,返回true,否则false
ORA_IS_CREATING_NESTED_TABLE 如果正在创建一个嵌套表则返回true,否则false
ORA_IS_DROP_COLUMN 如果删除的参数column_name指定的列,返回true,否则false
ORA_LOGIN_USER 触发器所在的用户名
ORA_PARTITION_POS SQL命令中可以正确添加分区子句位置
ORA_PRIVILEGE_LIST 授予或者回收的权限的数量。
ORA_REVOKEE 被回收者的数量
ORA_SQL_TXT 触发了触发器的SQL语句的行数。
ORA_SYSEVENT 导致DDL触发器被触发的时间
ORA_WITH_GRANT_OPTION 如果授权带有grant选项,返回true。否则false
更多属性函数请参考官方文档PL/SQL Language Reference -> Triggers and Oracle Database Data Transfer Utilities
使用事件和属性
Examples:
--创建数据库对象时发出警告,删除数据库对象时阻止
CREATE OR REPLACE TRIGGER HR.no_drop
BEFORE DDL ON DATABASE
BEGIN
IF ORA_SYSEVENT = 'CREATE'
THEN
DBMS_OUTPUT.PUT_LINE('Warning !!! You have created a '||
ORA_DICT_OBJ_TYPE ||' called '||
ORA_DICT_OBJ_NAME|| '; UserName(creater):'||
ORA_DICT_OBJ_OWNER||'; IP:'||
ORA_CLIENT_IP_ADDRESS||'; event:'||
ORA_SYSEVENT);
ELSIF ORA_SYSEVENT = 'DROP'
THEN
RAISE_APPLICATION_ERROR (-20000,
'Cannot create the ' || ORA_DICT_OBJ_TYPE ||
' named ' || ORA_DICT_OBJ_NAME ||
' as requested by ' || ORA_DICT_OBJ_OWNER);
END IF;
END;
--操作了数据库表的哪一列
CREATE OR REPLACE TRIGGER preserve_app_cols
AFTER ALTER ON SCHEMA
DECLARE
-- cursor to get columns in a table
CURSOR curs_get_columns (cp_owner VARCHAR2, cp_table VARCHAR2)
IS
SELECT column_name
FROM all_tab_columns
WHERE owner = cp_owner AND table_name = cp_table;
BEGIN
-- if it was a table that was altered...
IF ora_dict_obj_type = 'TABLE'
THEN
-- for every column in the table...
FOR v_column_rec IN curs_get_columns (
ora_dict_obj_owner,
ora_dict_obj_name
)
LOOP
-- if the current column was the one that was altered then say so
IF ora_is_alter_column (v_column_rec.column_name)
THEN
-- if the table/column is core?
IF is_application_column (
ora_dict_obj_owner,
ora_dict_obj_name,
v_column_rec.column_name
)
THEN
RAISE_APPLICATION_ERROR (
-20001,
'Cannot alter core application attributes'
);
END IF; -- table/column is core
END IF; -- current column was altered
END LOOP; -- every column in the table
END IF; -- table was altered
END;
--属性函数返回值列表
CREATE OR REPLACE TRIGGER hr.what_privs
AFTER GRANT ON SCHEMA
DECLARE
v_grant_type VARCHAR2 (30);
v_num_grantees BINARY_INTEGER;
v_grantee_list ora_name_list_t;
v_num_privs BINARY_INTEGER;
v_priv_list ora_name_list_t;
BEGIN
v_grant_type := ora_dict_obj_type;
v_num_grantees := ora_grantee (v_grantee_list);
v_num_privs := ora_privilege_list (v_priv_list);
IF v_grant_type = 'ROLE PRIVILEGE'
THEN
DBMS_OUTPUT.put_line (
CHR (9) || 'The following roles/privileges were granted'
);
FOR counter IN 1 .. v_num_privs
LOOP
DBMS_OUTPUT.put_line (
CHR (9) || CHR (9) || 'Privilege ' || v_priv_list (counter)
);
END LOOP;
ELSIF v_grant_type = 'OBJECT PRIVILEGE'
THEN
DBMS_OUTPUT.put_line (
CHR (9) || 'The following object privileges were granted'
);
FOR counter IN 1 .. v_num_privs
LOOP
DBMS_OUTPUT.put_line (
CHR (9) || CHR (9) || 'Privilege ' || v_priv_list (counter)
);
END LOOP;
DBMS_OUTPUT.put (CHR (9) || 'On ' || ora_dict_obj_name);
IF ora_with_grant_option
THEN
DBMS_OUTPUT.put_line (' with grant option');
ELSE
DBMS_OUTPUT.put_line ('');
END IF;
ELSIF v_grant_type = 'SYSTEM PRIVILEGE'
THEN
DBMS_OUTPUT.put_line (
CHR (9) || 'The following system privileges were granted'
);
FOR counter IN 1 .. v_num_privs
LOOP
DBMS_OUTPUT.put_line (
CHR (9) || CHR (9) || 'Privilege ' || v_priv_list (counter)
);
END LOOP;
ELSE
DBMS_OUTPUT.put_line ('I have no idea what was granted');
END IF;
FOR counter IN 1 .. v_num_grantees
LOOP
DBMS_OUTPUT.put_line (
CHR (9) || 'Grant Recipient ' || v_grantee_list (counter)
);
END LOOP;
END;
/
---------------------------------------------------------------------------------------------------------------------------------------------------
Oracle触发器4-数据库事件触发器
创建数据库事件触发器语法
1 CREATE [OR REPLACE] TRIGGER trigger_name
2 {BEFORE | AFTER} {database_event} ON {DATABASE | SCHEMA}
3 DECLARE
4 Variable declarations
5 BEGIN
6 ...some code...
7 END;
数据库事件触发器是发生在数据库范围的事件时触发的。有6个数据库事件触发器。
STARTUP
数据库打开时触发的。
没有before startup触发器。
Example:
CREATE OR REPLACE TRIGGER startup_pinner
AFTER STARTUP ON DATABASE
BEGIN
pin_plsql_packages;
pin_application_packages;
END;
SHUTDOWN
数据库正常关闭时触发的。
没有after shutdown 触发器。
Example:
CREATE OR REPLACE TRIGGER before_shutdown
BEFORE SHUTDOWN ON DATABASE
BEGIN
gather_system_stats;
END;
注意:只有在正常关闭情况下,shutdown nomal或者shutdown immediate时触发,非正常关闭shutdown abort不能触发。
SERVERERROR
当数据库发生错误时触发。
没有before servererror触发器。
Example:
DROP TRIGGER error_logger;
DROP TABLE error_log;
CREATE SEQUENCE error_seq;
CREATE TABLE error_log
(error_id NUMBER,
username VARCHAR2(30),
error_number NUMBER,
sequence NUMBER,
timestamp DATE);
CREATE OR REPLACE TRIGGER error_logger
AFTER SERVERERROR
ON SCHEMA
DECLARE
v_errnum NUMBER; -- the Oracle error #
v_now DATE := SYSDATE; -- current time
BEGIN
-- for every error in the error stack...
FOR e_counter IN 1..ORA_SERVER_ERROR_DEPTH LOOP
-- write the error out to the log table; no
-- commit is required because we are in an
-- autonomous transaction
INSERT INTO error_log(error_id,
username,
error_number,
sequence,
timestamp)
VALUES(error_seq.nextval,
USER,
ORA_SERVER_ERROR(e_counter),
e_counter,
v_now);
END LOOP; -- every error on the stack
END;
/
LOGON
当开始一个数据库会话时触发。
没有before logon触发器。
Example:
CREATE OR REPLACE TRIGGER after_logon
AFTER LOGON ON SCHEMA
DECLARE
v_sql VARCHAR2(100) := 'ALTER SESSION ENABLE RESUMABLE ' ||
'TIMEOUT 10 NAME ' || '''' ||
'OLAP Session' || '''';
BEGIN
EXECUTE IMMEDIATE v_sql;
DBMS_SESSION.SET_CONTEXT('OLAP Namespace',
'Customer ID',
load_user_customer_id);
END;
LOGOFF
当一个数据库会话正常终止时触发。
没有after logoff触发器。
Example:
CREATE OR REPLACE TRIGGER before_logoff
BEFORE LOGOFF ON DATABASE
BEGIN
gather_session_stats;
END;
DB_ROLE_CHANGE
当一个备用数据库切换成主数据库时或者反过来,触发。
多用于dataguard。