Oracle PL/SQL 触发器教程:Instead of、Compound [示例]

PL/SQL 中的触发器是什么?

触发器是存储程序,当在表上执行插入、更新、删除等 DML 语句或发生某些事件时,Oracle 引擎会自动触发它们。触发器代码的执行可以根据需要进行定义。您可以选择触发器需要触发的事件以及执行时间。触发器的目的是维护数据库信息的完整性。

触发器的优点

以下是触发器的优点。

  • 自动生成某些派生列值
  • 强制执行参照完整性
  • 事件日志记录和存储表访问信息
  • 审计
  • 表的同步复制
  • 强制执行安全授权
  • 防止无效事务

Oracle 中的触发器类型

触发器可以根据以下参数进行分类。

  • 基于时间的分类
  • BEFORE 触发器:在指定事件发生之前触发。
  • AFTER 触发器:在指定事件发生之后触发。
  • INSTEAD OF 触发器:特殊类型。您将在后面的主题中了解更多。 (仅用于 DML)
  • 基于级别的分类
  • 语句级触发器:为指定的事件语句触发一次。
  • 行级触发器:为指定事件中受影响的每条记录触发一次。 (仅用于 DML)
  • 基于事件的分类
  • DML 触发器:在指定 DML 事件 (INSERT/UPDATE/DELETE) 时触发
  • DDL 触发器:在指定 DDL 事件 (CREATE/ALTER) 时触发
  • 数据库触发器:在指定数据库事件 (LOGON/LOGOFF/STARTUP/SHUTDOWN) 时触发

因此,每个触发器都是以上参数的组合。

如何创建触发器

以下是创建触发器的语法。

Create Trigger

CREATE [ OR REPLACE ] TRIGGER <trigger_name> 

[BEFORE | AFTER | INSTEAD OF ]

[INSERT | UPDATE | DELETE......]

ON<name of underlying object>

[FOR EACH ROW] 

[WHEN<condition for trigger to get execute> ]

DECLARE
<Declaration part>
BEGIN
<Execution part> 
EXCEPTION
<Exception handling part> 
END;

语法说明

  • 上面的语法显示了触发器创建中存在的各种可选语句。
  • BEFORE/AFTER 指定事件时间。
  • INSERT/UPDATE/LOGON/CREATE/etc. 指定触发器需要触发的事件。
  • ON 子句指定上述事件在哪个对象上有效。例如,对于 DML 触发器,这将是可能发生 DML 事件的表名。
  • 命令“FOR EACH ROW”指定行级触发器。
  • WHEN 子句指定触发器需要触发的附加条件。
  • 声明部分、执行部分、异常处理部分与其他 PL/SQL 块相同。声明部分和异常处理部分是可选的。

:NEW 和 :OLD 子句

在行级触发器中,触发器会为每行相关记录触发。有时需要了解 DML 语句之前和之后的值。

Oracle 在 RECORD 级触发器中提供了两个子句来保存这些值。我们可以在触发器主体中使用这些子句来引用旧值和新值。

  • :NEW – 在触发器执行期间,它保存了基表/视图列的新值
  • :OLD – 在触发器执行期间,它保存了基表/视图列的旧值

此子句应根据 DML 事件使用。下表将指定哪个子句对哪个 DML 语句 (INSERT/UPDATE/DELETE) 有效。

INSERT UPDATE DELETE
:NEW 有效 有效 无效。删除情况没有新值。
:OLD 无效。插入情况没有旧值 有效 有效

INSTEAD OF 触发器

“INSTEAD OF 触发器”是一种特殊的触发器。它仅用于 DML 触发器。当对复杂视图发生任何 DML 事件时使用。

例如,一个视图由 3 个基表组成。当对该视图发出任何 DML 事件时,它将变得无效,因为数据来自 3 个不同的表。因此,这里使用了 INSTEAD OF 触发器。 INSTEAD OF 触发器用于直接修改基表,而不是修改给定事件的视图。

示例 1:在此示例中,我们将从两个基表创建一个复杂视图。

  • Table_1 是 emp 表,
  • Table_2 是 department 表。

然后我们将看到 INSTEAD OF 触发器如何用于对该复杂视图发出 UPDATE 位置详细信息语句。我们还将看到 :NEW 和 :OLD 在触发器中的用处。

  • 步骤 1:创建具有适当列的表“emp”和“dept”
  • 步骤 2:用示例值填充表
  • 步骤 3:为上述创建的表创建视图
  • 步骤 4:在 instead-of 触发器之前更新视图
  • 步骤 5:创建 instead-of 触发器
  • 步骤 6:在 instead-of 触发器之后更新视图

步骤 1) 创建具有适当列的表“emp”和“dept”

INSTEAD OF Trigger

CREATE TABLE emp(
emp_no NUMBER,
emp_name VARCHAR2(50),
salary NUMBER,
manager VARCHAR2(50),
dept_no NUMBER);
/

CREATE TABLE dept( 
Dept_no NUMBER, 
Dept_name VARCHAR2(50),
LOCATION VARCHAR2(50));
/

代码解释

  • 代码行 1-7:创建表 'emp'。
  • 代码行 8-12:创建表 'dept'。

输出

表已创建

步骤 2) 现在我们已经创建了表,我们将用示例值填充该表,并为上述表创建视图。

INSTEAD OF Trigger

BEGIN
INSERT INTO DEPT VALUES(10,‘HR’,‘USA’);
INSERT INTO DEPT VALUES(20,'SALES','UK’);
INSERT INTO DEPT VALUES(30,‘FINANCIAL',‘JAPAN'); 
COMMIT;
END;
/

BEGIN
INSERT INTO EMP VALUES(1000,'XXX5,15000,'AAA',30);
INSERT INTO EMP VALUES(1001,‘YYY5,18000,‘AAA’,20) ;
INSERT INTO EMP VALUES(1002,‘ZZZ5,20000,‘AAA',10); 
COMMIT;
END;
/

代码解释

  • 代码行 13-19:向 'dept' 表插入数据。
  • 代码行 20-26:向 'emp' 表插入数据。

输出

PL/SQL 过程已完成

步骤 3) 为上述创建的表创建视图。

INSTEAD OF Trigger

CREATE VIEW guru99_emp_view(
Employee_name:dept_name,location) AS
SELECT emp.emp_name,dept.dept_name,dept.location
FROM emp,dept
WHERE emp.dept_no=dept.dept_no;
/
SELECT * FROM guru99_emp_view;

代码解释

  • 代码行 27-32:创建 'guru99_emp_view' 视图。
  • 代码行 33:查询 guru99_emp_view。

输出

视图已创建

EMPLOYEE_NAME DEPT_NAME LOCATION
ZZZ HR 美国
YYY SALES UK
XXX FINANCIAL JAPAN

步骤 4) 在 instead-of 触发器之前更新视图。

INSTEAD OF Trigger

BEGIN
UPDATE guru99_emp_view SET location='FRANCE' WHERE employee_name=:'XXX’;
COMMIT;
END;
/

代码解释

  • 代码行 34-38:将“XXX”的位置更新为“FRANCE”。它引发了异常,因为DML 语句不允许在复杂视图中执行。

输出

ORA-01779: 无法修改映射到非键保留表的列

ORA-06512: 在第 2 行

步骤 5)为了避免上一步更新视图时遇到的错误,在此步骤我们将使用“instead of 触发器”。

INSTEAD OF Trigger

CREATE TRIGGER guru99_view_modify_trg
INSTEAD OF UPDATE
ON guru99_emp_view
FOR EACH ROW
BEGIN
UPDATE dept
SET location=:new.location
WHERE dept_name=:old.dept_name;
END;
/

代码解释

  • 代码行 39:为 'guru99_emp_view' 视图上的 'UPDATE' 事件创建 INSTEAD OF 触发器,发生在行级别。它包含用于更新基表 'dept' 中位置的 update 语句。
  • 代码行 44:Update 语句使用 ' :NEW ' 和 ' :OLD ' 来查找更新前后列的值。

输出

触发器已创建

步骤 6) 在 instead-of 触发器之后更新视图。现在不会出现错误,因为“instead of 触发器”将处理此复杂视图的更新操作。并且在代码执行时,员工 XXX 的位置将从“Japan”更新为“France”。

INSTEAD OF Trigger

BEGIN
UPDATE guru99_emp_view SET location='FRANCE' WHERE employee_name='XXX'; 
COMMIT;
END;
/
SELECT * FROM guru99_emp_view;

代码解释

  • 代码行 49-53:将“XXX”的位置更新为“FRANCE”。这是成功的,因为“INSTEAD OF”触发器阻止了对视图的实际更新语句,并执行了基表更新。
  • 代码行 55:验证更新的记录。

输出

PL/SQL 过程成功完成

EMPLOYEE_NAME DEPT_NAME LOCATION
ZZZ HR 美国
YYY SALES UK
XXX FINANCIAL FRANCE

复合触发器

复合触发器是一种触发器,它允许您在单个触发器主体中为四个时间点中的每一个指定操作。它支持的四个不同的时间点如下。

  • BEFORE STATEMENT – 级别
  • BEFORE ROW – 级别
  • AFTER ROW – 级别
  • AFTER STATEMENT – 级别

它提供了将不同时间点的操作组合到同一触发器中的功能。

Compound Trigger

CREATE [ OR REPLACE ] TRIGGER <trigger_name> 
FOR
[INSERT | UPDATE | DELET.......]
ON <name of underlying object>
<Declarative part>‭	‬
BEFORE STATEMENT IS
BEGIN
<Execution part>;
END BEFORE STATEMENT;

BEFORE EACH ROW IS
BEGIN
<Execution part>;
END EACH ROW;

AFTER EACH ROW IS
BEGIN
<Execution part>;
END AFTER EACH ROW;

AFTER STATEMENT IS
BEGIN
<Execution part>;
END AFTER STATEMENT;
END;

语法说明

  • 上面的语法显示了 'COMPOUND' 触发器的创建。
  • 声明部分是触发器主体中所有执行块的公共部分。
  • 这 4 个时间块可以按任何顺序排列。不一定必须包含所有这 4 个时间块。我们可以仅为所需的时序创建 COMPOUND 触发器。

示例 1:在此示例中,我们将创建一个触发器来自动填充 salary 列,默认值为 5000。

Compound Trigger

CREATE TRIGGER emp_trig 
FOR INSERT 
ON emp
COMPOUND TRIGGER 
BEFORE EACH ROW IS 
BEGIN
:new.salary:=5000;
END BEFORE EACH ROW;
END emp_trig;
/
BEGIN
INSERT INTO EMP VALUES(1004,‘CCC’,15000,‘AAA’,30); 
COMMIT;
END;
/
SELECT * FROM emp WHERE emp_no=1004;

代码解释

  • 代码行 2-10:创建复合触发器。它是在 BEFORE ROW 级别的时间点创建的,用于将 salary 填充为默认值 5000。这将在记录插入表之前将 salary 更改为默认值 '5000'。
  • 代码行 11-14:将记录插入 'emp' 表。
  • 代码行 16:验证插入的记录。

输出

触发器已创建

PL/SQL 过程成功完成。

EMP_NAME EMP_NO SALARY MANAGER DEPT_NO
CCC 1004 5000 AAA 30

启用和禁用触发器

触发器可以启用或禁用。要启用或禁用触发器,需要为禁用或启用它的触发器提供 ALTER (DDL) 语句。

以下是启用/禁用触发器的语法。

ALTER TRIGGER <trigger_name> [ENABLE|DISABLE];
ALTER TABLE <table_name> [ENABLE|DISABLE] ALL TRIGGERS;

语法说明

  • 第一个语法显示了如何启用/禁用单个触发器。
  • 第二个语句显示了如何启用/禁用特定表上的所有触发器。

摘要

在本章中,我们学习了 PL/SQL 触发器及其优点。我们还学习了不同的分类,并讨论了 INSTEAD OF 触发器和 COMPOUND 触发器。