1、数据库触发器 Trigger的概念

对数据库对象的操作可引发很多事件,比如before Insert,before update 等等,但这些事件产生的时候可以写响应代码来完成一些基于事件的操作,通常这些操作被写成一段Plsql程序;那么这些更具体的数据库对象上的事件相关的程序呢就称为数据库Trigger
实际应用场景举例(从EBS的发运确认产生的MMT 生成 POS收货确认单):
image.png
注意:除非迫不得已,尽量避免使用Trigger,因为这会导致维护困难

2、创建Trigger

Trigger的定义语句里面涉及到如下关键因素:

  • 时机:Before 或者 After 或 Instead of
  • 事件:Insert 或 Update 或 Delete
  • 对象:表名(或视图名)
  • 类型:Row 或者 Statement级;
  • 条件:满足特定Where条件才执行;
  • 内容:通常是一段PLSQL块代码;

重点注意:
Instead of : 用Trigger的内容替换 事件本身的动作
Row级:SQL语句影响到的每一行都会引发Trigger
Statement级:一句SQL语句引发一次,不管它影响多少行(甚至0行)

3、Trigger的触发顺序

A.创建Statement 级别Trigger 语法

1,语法

  1. CREATE [OR REPLACE] TRIGGER trigger_name
  2. timing
  3. event1 [OR event2 OR event3]
  4. ON table_name
  5. trigger_body

2,举例

  1. CREATE OR REPLACE TRIGGER secure_emp
  2. BEFORE INSERT ON employees
  3. BEGIN
  4. IF (TO_CHAR(SYSDATE,'DY') IN ('SAT','SUN')) OR
  5. (TO_CHAR(SYSDATE,'HH24:MI') NOT BETWEEN '08:00' AND '18:00')
  6. THEN RAISE_APPLICATION_ERROR (-20500,'You may insert into EMPLOYEES table only during business hours.');
  7. END IF;
  8. END;
  9. /

B.多事件Trigger,在Trigger Body中判断具体事件

  1. CREATE OR REPLACE TRIGGER secure_emp
  2. BEFORE INSERT OR UPDATE OR DELETE ON employees
  3. BEGIN
  4. IF (TO_CHAR (SYSDATE,'DY') IN ('SAT','SUN')) OR
  5. (TO_CHAR (SYSDATE, 'HH24') NOT BETWEEN '08' AND '18')
  6. THEN
  7. IF DELETING THEN
  8. RAISE_APPLICATION_ERROR (-20502,'You may delete from EMPLOYEES
  9. table only during business hours.');
  10. ELSIF INSERTING THEN
  11. RAISE_APPLICATION_ERROR (-20500,'You may insert into
  12. EMPLOYEES table only during business hours.');
  13. ELSIF UPDATING ('SALARY') THEN
  14. RAISE_APPLICATION_ERROR (-20503,'You may update
  15. SALARY only during business hours.');
  16. ELSE
  17. RAISE_APPLICATION_ERROR (-20504,'You may update
  18. EMPLOYEES table only during normal hours.');
  19. END IF;
  20. END IF;
  21. END;

C.创建Row级别Trigger 语法

1,语法

  1. CREATE [OR REPLACE] TRIGGER trigger_name
  2. timing
  3. event1 [OR event2 OR event3]
  4. ON table_name
  5. [REFERENCING OLD AS old | NEW AS new]
  6. FOR EACH ROW
  7. [WHEN (condition)]
  8. trigger_body

2,举例

  1. CREATE OR REPLACE TRIGGER restrict_salary
  2. BEFORE INSERT OR UPDATE OF salary ON employees
  3. FOR EACH ROW
  4. BEGIN
  5. IF NOT (:NEW.job_id IN ('AD_PRES', 'AD_VP'))
  6. AND :NEW.salary > 15000
  7. THEN
  8. RAISE_APPLICATION_ERROR (-20202,'Employee cannot earn this amount');
  9. END IF;
  10. END;
  11. /

4、使用OLDNEW修饰词

  1. CREATE OR REPLACE TRIGGER audit_emp_values
  2. AFTER DELETE OR INSERT OR UPDATE ON employees
  3. FOR EACH ROW
  4. BEGIN
  5. INSERT INTO audit_emp_table (user_name, timestamp,
  6. id, old_last_name, new_last_name, old_title,
  7. new_title, old_salary, new_salary)
  8. VALUES (USER, SYSDATE, :OLD.employee_id,
  9. :OLD.last_name, :NEW.last_name, :OLD.job_id,
  10. :NEW.job_id, :OLD.salary, :NEW.salary );
  11. END;
  12. /

5、使用WHEN限制Trigger的触发条件

  1. CREATE OR REPLACE TRIGGER derive_commission_pct
  2. BEFORE INSERT OR UPDATE OF salary ON employees
  3. FOR EACH ROW
  4. WHEN (NEW.job_id = 'SA_REP')
  5. BEGIN
  6. IF INSERTING
  7. THEN :NEW.commission_pct := 0;
  8. ELSIF :OLD.commission_pct IS NULL
  9. THEN :NEW.commission_pct := 0;
  10. ELSE
  11. :NEW.commission_pct := :OLD.commission_pct + 0.05;
  12. END IF;
  13. END;
  14. /

6、INSTEAD OF Trigger的运行原理图解

image.png
INSTEAD OF Trigger的使用场景:建立在View上的Trigger 通常是INSTEAD OF类型的,因为复杂视图不能被 直接更改; 意义不大,很少使用;

7、管理Trigger

A.失效/生效

  1. ALTER TRIGGER trigger_name DISABLE | ENABLE

B.批量失效/生效

  1. ALTER TABLE table_name DISABLE | ENABLE ALL TRIGGERS

C.重新编译

  1. ALTER TRIGGER trigger_name COMPILE

D.删除

  1. DROP TRIGGER trigger_name;

8、约束与Trigger同时存在,那个先执行?

  1. UPDATE employees SET department_id = 999
  2. WHERE employee_id = 170;
  3. -- Integrity constraint violation error
  4. CREATE OR REPLACE TRIGGER constr_emp_trig
  5. AFTER UPDATE ON employees
  6. FOR EACH ROW
  7. BEGIN
  8. INSERT INTO departments
  9. VALUES (999, 'dept999', 140, 2400);
  10. END;
  11. /
  12. UPDATE employees SET department_id = 999
  13. WHERE employee_id = 170;
  14. -- Successful after trigger is fired

9、Database的 Trigger

除了针对Table,View的Trigger,还有针对Database的 Trigger,比如 :写Triger记录所有登录/登出数据库的用户

  1. CREATE OR REPLACE TRIGGER logon_trig
  2. AFTER LOGON ON SCHEMA
  3. BEGIN
  4. INSERT INTO log_trig_table(user_id, log_date, action)
  5. VALUES (USER, SYSDATE, 'Logging on');
  6. END;
  7. /
  1. CREATE OR REPLACE TRIGGER logoff_trig
  2. BEFORE LOGOFF ON SCHEMA
  3. BEGIN
  4. INSERT INTO log_trig_table(user_id, log_date, action)
  5. VALUES (USER, SYSDATE, 'Logging off');
  6. END;
  7. /

10、冲突表(Mutating Table)

当某张表上的针对DML动作的Trigger需要访问到表自身的数据时,对Trigger来说,这就是一张冲突表
对于employee表的新增或者更改Salary动作引发的Trigger,该Trigger又要读取Salary的Min值,这就冲突了。

  1. CREATE OR REPLACE TRIGGER check_salary
  2. BEFORE INSERT OR UPDATE OF salary, job_id
  3. ON employees
  4. FOR EACH ROW
  5. WHEN (NEW.job_id <> 'AD_PRES')
  6. DECLARE
  7. v_minsalary employees.salary%TYPE;
  8. v_maxsalary employees.salary%TYPE;
  9. BEGIN
  10. SELECT MIN(salary), MAX(salary)
  11. INTO v_minsalary, v_maxsalary
  12. FROM employees
  13. WHERE job_id = :NEW.job_id;
  14. IF :NEW.salary < v_minsalary OR
  15. :NEW.salary > v_maxsalary THEN
  16. RAISE_APPLICATION_ERROR(-20505,
  17. 'Out of range');
  18. END IF;
  19. END;
  20. /

11、将Trigger用于数据变更审计日志

Oracle内置数据更改审计的功能: 即更改动作可以被记录到审计记录表 sys.Audit$ .
要让审计功能其作用,必须:
1、设置数据库参数:audit_trail = DB 或者 OS (默认是NONE)
2、设置要审计的对象:比如

  1. AUDIT INSERT, UPDATE, DELETE
  2. ON stu3693.copy_emp
  3. BY ACCESS
  4. WHENEVER SUCCESSFUL;

通常情况下,客户方的DBA从性能角度考虑,不愿意启用数据库的审计功能,但某些客户认为某张表的变更记录 非常重要,一定要有审计功能可考虑使用Trigger作为替代方案,比如:

  1. CREATE OR REPLACE TRIGGER audit_emp_values
  2. AFTER DELETE OR INSERT OR UPDATE ON employees
  3. FOR EACH ROW
  4. BEGIN
  5. IF (audit_emp_package.g_reason IS NULL) THEN
  6. RAISE_APPLICATION_ERROR (-20059, 'Specify a reason
  7. for the data operation through the procedure SET_REASON
  8. of the AUDIT_EMP_PACKAGE before proceeding.');
  9. ELSE
  10. INSERT INTO audit_emp_table (user_name, timestamp, id,
  11. old_last_name, new_last_name, old_title, new_title,
  12. old_salary, new_salary, comments)
  13. VALUES (USER, SYSDATE, :OLD.employee_id, :OLD.last_name,
  14. :NEW.last_name, :OLD.job_id, :NEW.job_id, :OLD.salary,
  15. :NEW.salary, audit_emp_package.g_reason);
  16. END IF;
  17. END;