一、使用条件谓词

在上文中,看到了一个触发器,可以防止在周末插入EMPLOYEES:

CREATE OR REPLACE TRIGGER secure_emp
BEFORE INSERT ON employees BEGINIF TO_CHAR(SYSDATE,'DY') IN ('SAT','SUN') THENRAISE_APPLICATION_ERROR(-20500,'You may insert into EMPLOYEES'||' table only during business hours');END IF;
END;

假设希望在周末期间阻止EMPLOYEES上的任何DML操作,并为INSERT,UPDATE和DELETE提供不同的错误消息。 可以创建三个单独的触发器; 不过,也可以用一个触发器来做到这一点。

CREATE OR REPLACE TRIGGER secure_emp
BEFORE INSERT OR UPDATE OR DELETE ON employees
BEGINIF TO_CHAR(SYSDATE,'DY') IN ('SAT','SUN') THENIF DELETING THEN RAISE_APPLICATION_ERROR(-20501,'You may delete from EMPLOYEES'||' table only during business hours');ELSIF INSERTING THEN RAISE_APPLICATION_ERROR(-20502,'You may insert into EMPLOYEES'||' table only during business hours');ELSIF UPDATING THEN RAISE_APPLICATION_ERROR(-20503,'You may update EMPLOYEES'||' table only during business hours');END IF;
END IF;
END;

可以使用条件谓词来测试特定列上的UPDATE:

CREATE OR REPLACE TRIGGER secure_emp
BEFORE UPDATE ON employees
BEGINIF UPDATING('SALARY') THENIF TO_CHAR(SYSDATE,'DY') IN ('SAT','SUN')THEN RAISE_APPLICATION_ERROR(-20501,'You may update SALARY'||' only during business hours');END IF;ELSIF UPDATING('JOB_ID') THENIF TO_CHAR(SYSDATE,'DY') = 'SUN'THEN RAISE_APPLICATION_ERROR(-20502,'You may not update JOB_ID on Sunday');END IF;
END IF;
END;

二、了解行触发器

请记住,对于每个触发的DML语句,语句触发器只执行一次:

CREATE OR REPLACE TRIGGER log_emps
AFTER UPDATE OF salary ON employees BEGIN
INSERT INTO log_emp_table (who, when)VALUES (USER, SYSDATE);
END;

无论触发语句是更新一个员工,几个员工,还是根本没有员工,该触发器都会在日志表中正好插入一行。

假设您想为每个更新的员工在日志表中插入一行。 例如,如果更新了四名员工,则将四行插入日志表中。 你需要一个行触发器。

(1)行触发器触发序列

对于受触发DML语句影响的每一行,行触发器都会触发(执行)一次,无论是在处理该行之前还是仅在AFTER之后。 如果五个员工在部门50中,则行触发器执行五次:

UPDATE employeesSET salary = salary * 1.1WHERE department_id = 50;

(2)创建行触发器

CREATE OR REPLACE TRIGGER log_emps
AFTER UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
INSERT INTO log_emp_table (who, when)VALUES (USER, SYSDATE);
END;

可以使用FOR EACH ROW来指定行触发器。 上一张幻灯片中的UPDATE语句现在将五行插入日志表中,每个EMPLOYEE行更新一行。但是,日志表中的所有五行都是相同的。 日志表不显示哪些员工已更新,或者他们的薪水有哪些变化。

(3)使用:OLD和:NEW限定符

只有在行触发器中,您是否可以在当前正在更新的EMPLOYEES行中引用和使用旧列和新列值。
    代码:OLD.column_name引用preupdate值,以及:NEW.column_name引用更新后的值。

例如,如果UPDATE语句将雇员的工资从10000更改为11000,则:OLD.salary的值为10000,以及:NEW.salary的值为11000.现可以将所需的数据插入日志记录表。

要记录employee_id,无论是否编码:OLD.employee_id或:NEW.employee_id,都无关紧要吗?有区别吗?

CREATE OR REPLACE TRIGGER log_emps
AFTER UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
INSERT INTO log_emp_table(who, when, which_employee, old_salary, new_salary)VALUES (USER, SYSDATE, :OLD.employee_id,:OLD.salary, :NEW.salary);
END;

行触发器的第二个例子

CREATE OR REPLACE TRIGGER audit_emp_values
AFTER DELETE OR INSERT OR UPDATE ON employees
FOR EACH ROW
BEGININSERT INTO audit_emp(user_name, time_stamp, id,old_last_name, new_last_name, old_title,new_title, old_salary, new_salary)VALUES (USER, SYSDATE, :OLD.employee_id,:OLD.last_name, :NEW.last_name, :OLD.job_id,:NEW.job_id, :OLD.salary, :NEW.salary);
END;

第二个示例:测试audit_emp_values触发器

INSERT INTO employees
(employee_id, last_name, job_id, salary, ...)VALUES (999, 'Temp emp', 'SA_REP', 1000,...);UPDATE employees
SET salary = 2000, last_name = 'Smith'
WHERE employee_id = 999;
SELECT user_name, time_stamp, ...FROM audit_emp;

行触发器的第三个例子

假设你需要防止不是总裁或副总裁的雇员的工资超过15000美元。

CREATE OR REPLACE TRIGGER restrict_salary
BEFORE INSERT OR UPDATE OF salary ON employees
FOR EACH ROW
BEGINIF NOT (:NEW.job_id IN ('AD_PRES', 'AD_VP'))AND :NEW.salary > 15000 THENRAISE_APPLICATION_ERROR (-20202,'Employee cannot earn more than $15,000.');END IF;
END;

测试restrict_salary触发器:

UPDATE employees SET salary = 15500WHERE last_name IN ('King','Davies');

King是(副)主席,但Davies不是。 此UPDATE语句会产生以下错误:

ORA-20202: Employee cannot earn more than $15,000.
ORA-06512: at “USVA_TEST_SQL01_T01.RESTRICT_SALARY”, line 4
ORA-04088: error during execution of trigger
‘USVA_TEST_SQL01_T01.RESTRICT_SALARY’
2. WHERE last_name IN (‘King’, ‘Davies’);

EMPLOYEES行都不会更新,因为UPDATE语句必须完全成功或根本不成功。

第四个例子:用触发器实现完整性约束

EMPLOYEES表在DEPARTMENTS表的DEPARTMENT_ID列中具有外键约束。 DEPARTMENT_ID 999不存在,因此此DML语句违反了约束条件,员工行未更新:

UPDATE employees SET department_id = 999
WHERE employee_id = 124; 

可以使用触发器自动创建新部门。

第四个例子:创建触发器:

CREATE OR REPLACE TRIGGER employee_dept_fk_trg
BEFORE UPDATE OF department_id ON employees
FOR EACH ROW
DECLAREv_dept_id departments.department_id%TYPE;
BEGINSELECT department_id INTO v_dept_id FROM departmentsWHERE department_id = :NEW.department_id;
EXCEPTIONWHEN NO_DATA_FOUND THENINSERT INTO departments VALUES(:NEW.department_id,'Dept '||:NEW.department_id, NULL, NULL);

来测试它:

UPDATE employees SET department_id = 999
WHERE employee_id = 124;
-- Successful after trigger is fired

三、使用REFERENCING子句

再看一下行触发器的第一个例子:

CREATE OR REPLACE TRIGGER log_emps
AFTER UPDATE OF salary ON employees
FOR EACH ROW
BEGININSERT INTO log_emp_table(who, when, which_employee, old_salary, new_salary)VALUES (USER, SYSDATE, :OLD.employee_id,:OLD.salary, :NEW.salary);
END;

如果EMPLOYEES表的名称不同,该怎么办?
如果它被称为OLD呢? OLD不是一个好名字,但是可能的。 我们的代码现在会是什么样子?

OLD现在意味着两件事:它是一个值限定符(如:NEW),也是一个表名。 该代码将起作用,但会令人困惑。 我们不需要

使用:OLD和:NEW。 我们可以通过包含REFERENCING子句来使用不同的限定符。

CREATE OR REPLACE TRIGGER log_emps
AFTER UPDATE OF salary ON old
REFERENCING OLD AS former NEW AS latter
FOR EACH ROW
BEGININSERT INTO log_emp_table(who, when, which_employee, old_salary, new_salary)VALUES (USER, SYSDATE, :former.employee_id,:former.salary, :latter.salary);
END;

FORMER和LATTER被称为关联名称。 他们是OLD和NEW的别名。 我们可以选择任何我们喜欢的相关名称(例如TOM和MARY),只要它们不是保留字。 REFERENCING子句只能用于行触发器。

四、使用WHEN子句

看看这个触发代码。 只有在新薪水高于旧薪水时才会记录薪资变化。

CREATE OR REPLACE TRIGGER restrict_salary
AFTER UPDATE OF salary ON employees
FOR EACH ROW
BEGINIF :NEW.salary > :OLD.salary THENINSERT INTO log_emp_table(who, when, which_employee, old_salary, new_salary)VALUES (USER, SYSDATE, :OLD.employee_id,:OLD.salary, :NEW.salary);END IF;
END;

整个触发器主体是一个单一的IF语句。 在现实生活中,这可能是许多代码行,包括CASE语句,循环和许多其他构造。 这将很难阅读。

可以在触发器标题中编写我们的IF条件,就在BEGIN子句之前。

CREATE OR REPLACE TRIGGER restrict_salary
AFTER UPDATE OF salary ON employees
FOR EACH ROW
WHEN (NEW.salary > OLD.salary)
BEGININSERT INTO log_emp_table(who, when, which_employee, old_salary, new_salary)VALUES (USER, SYSDATE, :OLD.employee_id,:OLD.salary, :NEW.salary);
END;

这段代码更容易阅读,特别是如果触发器体长且复杂。 WHEN子句只能用于行触发器。

五、INSTEAD OF触发器

复杂视图(例如基于联接的视图)无法更新。 假设EMP_DETAILS视图是基于EMPLOYEES和DEPARTMENTS联合的复杂视图。 以下SQL语句失败:

INSERT INTO emp_detailsVALUES (9001,'ABBOTT',3000, 10, 'Administration');

可以通过创建一个触发器来直接更新两个基表,而不是尝试(和失败)更新视图。

INSTEAD OF触发器总是行触发器。

(1)一个INSTEAD OF触发器的例子

将INSERT执行到基于NEW_EMPS和NEW_DEPTS表的EMP_DETAILS视图中:

INSERT INTO emp_detailsVALUES (9001,'ABBOTT',3000, 10, 'Administration');

(2)创建一个INSTEAD OF触发器

步骤1:创建表格和复杂视图:

CREATE TABLE new_emps AS
SELECT employee_id,last_name,salary,department_idFROM employees;
CREATE TABLE new_depts AS
SELECT d.department_id,d.department_name,sum(e.salary) dept_salFROM employees e, departments dWHERE e.department_id = d.department_idGROUP BY d.department_id,d.department_name;
CREATE VIEW emp_details AS
SELECT e.employee_id, e.last_name, e.salary,e.department_id, d.department_nameFROM new_emps e, new_depts dWHERE e.department_id = d.department_id; 

第2步:创建INSTEAD OF触发器:

CREATE OR REPLACE TRIGGER new_emp_dept
INSTEAD OF INSERT ON emp_details
BEGININSERT INTO new_empsVALUES (:NEW.employee_id, :NEW.last_name,:NEW.salary, :NEW.department_id);UPDATE new_deptsSET dept_sal = dept_sal + :NEW.salaryWHERE department_id = :NEW.department_id;
END;

(3)行触发器重访

看看这个行触发器,记录员工的工资变化:

CREATE OR REPLACE TRIGGER log_emps
AFTER UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
INSERT INTO log_table(employee_id, change_date, salary)VALUES (:OLD.employee_id, SYSDATE, :NEW.salary);
END;

如果有一百万名员工,并且你给每个员工5%的工资增长:

UPDATE employees SET salary = salary * 1.05;

行触发器将自动执行一百万次,每次插入一行。 这将非常缓慢。

在课程的前期,学习了如何使用批量绑定(FORALL)来加速DML。 我们可以在我们的触发器中使用FORALL吗?

CREATE OR REPLACE TRIGGER log_emps
AFTER UPDATE OF salary ON employees
FOR EACH ROW
DECLARETYPE t_log_emp IS TABLE OF log_table%ROWTYPEINDEX BY BINARY_INTEGER;log_emp_tab t_log_emp;
BEGIN... Populate log_emp_tab with employees’ change data
FORALL i IN log_emp_tab.FIRST..log_emp_tab.LASTINSERT INTO log_table VALUES log_emp_tab(i);
END;

这不起作用。 为什么不? 提示:请记住,这是一个行触发器,并考虑LOG_EMP_TAB收集变量的作用域。

CREATE OR REPLACE TRIGGER log_emps
AFTER UPDATE OF salary ON employees
FOR EACH ROW
DECLARETYPE t_log_emp IS TABLE OF log_table%ROWTYPEINDEX BY BINARY_INTEGER;log_emp_tab t_log_emp;
BEGIN... Populate log_emp_tab with employees’ change data
FORALL i IN log_emp_tab.FIRST..log_emp_tab.LASTINSERT INTO log_table VALUES log_emp_tab(i);
END;

在触发器的每次执行结束时,触发器变量会丢失范围。 所以每次触发行触发器时,LOG_EMP_TAB中已收集的所有数据都将丢失。为了避免丢失这些数据,我们需要一个只触发一次的触发器 - 一个语句触发器。 但要引用每行的列值(使用:OLD和:NEW),我们需要一个行触发器。

但是单个触发器不能同时是行触发器和语句触发器。 对? 错误! 我们创建一个复合触发器。

六、什么是复合触发器?

一个触发器,可以包含针对每个可能的时间点的操作:触发语句之前,每行之前,每行之后,触发语句之后。 复合触发器有一个声明部分,以及每个时间点的部分。 你不必包含所有的时间点,只需要你需要的时间点。 复合触发器变量的范围是整个触发器,因此它们在整个执行过程中保留其范围。

(1)复合触发结构

(2)例子

这个例子有一个声明部分和四个可能的时间点部分中的两个。

(3)完整代码

CREATE OR REPLACE TRIGGER log_emps
FOR UPDATE OF salary ON employees
COMPOUND TRIGGER
DECLARETYPE t_log_emp IS TABLE OF log_table%ROWTYPEINDEX BY BINARY_INTEGER;log_emp_tab t_log_emp;v_index BINARY_INTEGER := 0;
AFTER EACH ROW IS BEGINv_index := v_index + 1;log_emp_tab(v_index).employee_id := :OLD.employee_id;log_emp_tab(v_index).change_date := SYSDATE;log_emp_tab(v_index).salary := :NEW.salary;
END AFTER EACH ROW;
AFTER STATEMENT IS BEGIN
FORALL I IN log_emp_tab.FIRST..log_emp_tab.LASTINSERT INTO log_table VALUES log_emp_tab(i);
END AFTER STATEMENT;
END log_emps;

Oracle入门(十四.21)之创建DML触发器:第二部分相关推荐

  1. 视觉SLAM十四讲从理论到实践第二版源码调试笔记(理论基础1-6章)

    2019-2020-2学期机器人工程专业需要开设SLAM技术课程,使用教材为视觉SLAM十四讲从理论到实践第二版. 为方便学生学习课程知识,将Arduino.ROS1.ROS2和SLAM集成到课程定制 ...

  2. Oracle入门(十四.20)之创建DML触发器:第一部分

    一.什么是DML触发器? DML触发器是执行SQL DML语句(INSERT,UPDATE或DELETE)时自动触发(执行)的触发器. 您可以通过两种方法对DML触发器进行分类: •执行时间:BEFO ...

  3. Oracle入门(十四.23)之管理触发器

    一.触发器需要特权 要在模式中创建触发器,需要: •CREATE TRIGGER系统特权 •触发器主体中引用的其他架构中的对象的普通对象特权(SELECT,UPDATE,EXECUTE等) •与触发器 ...

  4. oracle创建dml触发器,Oracle数据库创建DML触发器

    触发器的基本分类 1.行触发器:数据库表中的每一行有变化都会触发一次触发器代码 2.语句触发器:与语句所影响的行数无关,仅触发一次 3.BEFORE触发器:在DML语句执行之前触发 4.ALFTER触 ...

  5. java怎样用类模板创建对象_java入门(十四) | 面向对象(OOP)之类和对象

    上一期是变量,在java中变量总是无处不在,而变量其意就是可以改变的数,在一般情况下我们可以以变量类型,变量名,变量值来描述它 这一期是给面向对象(OOP)开了一个头,对他的概念,三大特征有了一个基础 ...

  6. Node学习十四 —— 使用node创建HTTP请求

    创建HTTP连接 Node擅长处理I/O操作,所以它不仅合适提供HTTP服务,也适合使用这些服务.接下来你将学习使用http模块和第三方模块执行和控制http请求. 在HTTP协议中,有两个重要的属性 ...

  7. Oracle入门(四)之查询基本信息

    一.查询基本信息 (1) 查询实例服务 SQL> show parameter instance name (2)查询数据库名字 SQL> show parameter db_name; ...

  8. Helm 3 完整教程(二十四):创建和使用子 chart

    推荐阅读 Helm3(K8S 资源对象管理工具)视频教程:https://edu.csdn.net/course/detail/32506 Helm3(K8S 资源对象管理工具)博客专栏:https: ...

  9. Oracle笔记 十四、查询XML操作、操作系统文件

    --1.随机数 select dbms_random.value from dual; select mod(dbms_random.random, 10) from dual; --0-9随机数 s ...

最新文章

  1. 如何分析apache日志[access_log(访问日志)和error_log(错误日志)]
  2. 28行满分代码:L1-048 矩阵A乘以B (15分)
  3. linux中mysql导入数据库命令_linux下mysql数据库导入导出命令
  4. Python使用笔记总结目录
  5. python命令行tab实例小妙招
  6. 怎么用c51语言写正弦波,单片机入门-C51语言用DAC0832实现正弦波的波形发生器第二节...
  7. 集成电路和芯片的联系与区别
  8. 关键系统进程 C:\Windows\system32\lsass.exe 失败,状态代码是 255。现在必须重新启动计算机。
  9. NSIS教程(4): 调用Windows API
  10. 电脑开机后网络一直转圈,程序也打不开——亲测解决办法
  11. 三分钟读懂新一代人工智能——ChatGPT
  12. VisionPro基础入门
  13. Coursera机器学习第三周Regularization练习题
  14. 魅蓝note6的android怎么升级,魅蓝note6怎么把应用安装到sd卡
  15. 新课程改革的理论基础究竟是什么
  16. JAVA培训之数据库表关联关系
  17. 前端性能优化,之还在为多种多样的知识点整理苦恼吗,进来看看吧。
  18. VB多层防火墙技术的研究-状态检测
  19. 10个优秀的日志分析工具
  20. Pthreads并行编程之spin lock与mutex性能对比分析

热门文章

  1. [Java基础]反射获取构造方法并使用
  2. [Java基础]多态基础
  3. [蓝桥杯][基础练习VIP]Huffuman树
  4. 上元节的灯会(亮)-dfs
  5. Piggy-Bank POJ - 1384(完全背包+背包放满)
  6. java1.8的stream_JDK1.8新特性(一):stream
  7. 排查生产问题linux命令,排查问题所用到的一些Linux命令实践(不定期更新。。)...
  8. 快速傅里叶变换(完整推导过程 + 模板)
  9. Educational Codeforces Round 76 (Rated for Div. 2) F. Make Them Similar 折半搜索
  10. 【CF1020C】Election【贪心】