迁移 Oracle 到 PostgreSQL: 触发器

七月 29, 2023

摘要:触发器是存储在数据库中并在发生指定事件时触发的过程。导致触发器运行的关联事件可以绑定到特定的数据库表、数据库视图、数据库模式或数据库本身。

Oracle 用法

触发器可以在以下之后运行:

  • 数据操作语言(DML)语句,如DELETEINSERTUPDATE
  • 数据定义语言(DDL)语句,如CREATEALTERDROP
  • 数据库事件和操作,如SERVERERRORLOGONLOGOFFSTARTUPSHUTDOWN

触发器类型

  • DML 触发器可以在表或视图上创建,并在插入、更新或删除数据时触发。触发器可以在 DML 命令运行之前或之后触发。
  • 可以在不可编辑的视图上创建 INSTEAD OF 触发器。INSTEAD OF触发器提供了一种应用程序透明的方法,用于修改无法由 DML 语句修改的视图。
  • SYSTEM 事件触发器在数据库或架构级别定义,包括在特定事件后触发的触发器:
    • 用户登录和注销。
    • 数据库事件(启动/关闭)、DataGuard 事件、服务器错误。

例子

创建一个触发器,该触发器在从表PROJECTS中删除行后运行,或者在项目的主键更新时运行。

CREATE OR REPLACE TRIGGER PROJECTS_SET_NULL
  AFTER DELETE OR UPDATE OF PROJECTNO ON PROJECTS
  FOR EACH ROW
  BEGIN
    IF UPDATING AND :OLD.PROJECTNO != :NEW.PROJECTNO OR DELETING THEN
      UPDATE EMP SET EMP.PROJECTNO = NULL
      WHERE EMP.PROJECTNO = :OLD.PROJECTNO;
    END IF;
END;
/
Trigger created.
DELETE FROM PROJECTS WHERE PROJECTNO=123;

SELECT PROJECTNO FROM EMP WHERE PROJECTNO=123;
PROJECTNO
NULL

在表上创建系统/模式触发器。如果为HR模式中的对象运行DROP类型的 DDL 命令,则触发器将触发。它可以防止丢弃对象并引发应用程序错误。

CREATE OR REPLACE TRIGGER PREVENT_DROP_TRIGGER
  BEFORE DROP ON HR.SCHEMA
BEGIN
    RAISE_APPLICATION_ERROR (num => -20000,
    msg => 'Cannot drop object');
END;
/
Trigger created.
DROP TABLE HR.EMP
ERROR at line 1:
ORA-00604: error occurred at recursive SQL level 1
ORA-20000: Cannot drop object
ORA-06512: at line 2

有关详细信息,请参阅 Oracle 文档中的 CREATE TRIGGER 语句

PostgreSQL 用法

触发器是存储在数据库中并在发生指定事件时触发的过程。PostgreSQL 中的 DML 触发器共享 Oracle 触发器中存在的许多功能。

  • DML 触发器(基于与表相关的事件(如 DML)触发的触发器)。
  • 事件触发器(在某些数据库事件(如运行 DDL 命令)之后触发的触发器)。

与 Oracle 触发器不同,PostgreSQL 触发器必须调用函数,并且不支持将 PL/pgSQL 代码的匿名块作为触发器主体的一部分。用户提供的函数声明时不带参数,并且返回类型为触发器。

PostgreSQL 创建触发器概要

CREATE [ CONSTRAINT ] TRIGGER name { BEFORE | AFTER | INSTEAD OF } { event [ OR ... ]}
  ON table_name
  [ FROM referenced_table_name ]
  [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
  [ REFERENCING { { OLD | NEW } TABLE [ AS ] transition_relation_name } [ ... ] ]
  [ FOR [ EACH ] { ROW | STATEMENT } ]
  [ WHEN ( condition ) ]
  EXECUTE PROCEDURE function_name ( arguments )

where event can be one of:
  INSERT
  UPDATE [ OF column_name [, ... ] ]
  DELETE
  TRUNCATE

注意

REFERENCING是 PostgreSQL 10 中引入的新选项。可以将此选项与AFTER触发器一起使用,以与OLDNEW TABLE更改行的整体视图进行交互。

在某些情况下,可以多次触发多个触发器。这包括未计划运行的触发器,例如:

  • 带有ON CONFLICT DO UPDATE子句的INSERT语句可能会导致插入和更新操作触发。
  • 由外键强制导致UPDATEDELETE可以触发触发器。例如,ON UPDATE CASCADE或者ON DELETE SET NULL可以触发应该由表上的UPDATEDELETE命令触发的触发器。

PostgreSQL DML 触发器

PostgreSQL触发器可以在 DML 操作之前或之后运行。

  • 在尝试操作一行数据之前触发。
    • 在约束检查之前,并尝试INSERTUPDATEDELETE时。
    • 如果触发器在事件之前或代替事件触发,则触发器可以跳过当前行的操作或更改要插入的行(仅限INSERTUPDATE操作)。
  • 操作完成后,在约束检查完并完成INSERTUPDATEDELETE命令之后。如果触发器在事件发生后触发,则触发器可以看到所有更改,包括其他触发器的效果。

PostgreSQL 触发器可以在视图上创建时运行INSTEAD OF形式的 DML 命令。

PostgreSQL 触发器可以针对受 DML 语句影响的每行记录(FOR EACH ROW)运行,也可以作为 DML 语句的一部分只运行一次(FOR EACH STATEMENT)。

触发时间 数据库事件 行级触发器 语句级触发器
之前 INSERT, UPDATE, DELETE 表和外表 表、视图和外部表
之前 TRUNCATE 不适用
之后 INSERT, UPDATE, DELETE 表和外表 表、视图和外部表
之后 TRUNCATE 不适用
替代执行 INSERT, UPDATE, DELETE 视图 不适用
替代执行 TRUNCATE 不适用 不适用

PostgreSQL 事件触发器

当数据库中发生与触发器关联的特定事件时,将运行事件触发器。支持的事件包括:ddl_command_startddl_command_endtable_rewritesql_drop

  • ddl_command_start在运行CREATEALTERDROPSECURITY LABELCOMMENTGRANTREVOKESELECT INTO命令之前发生。
  • ddl_command_end在命令完成之后和事务提交之前发生。
  • sql_drop仅针对 DROP 类型的 DDL 命令触发。在ddl_command_end前触发。

有关详细信息,请参阅 PostgreSQL 文档中的事件触发器触发矩阵

例子

创建 DML 触发器。要在 PostgreSQL 中创建 Oracle DML 触发器的等效版本,请首先创建一个函数触发器,该触发器将存储触发器的运行逻辑。

CREATE OR REPLACE FUNCTION PROJECTS_SET_NULL()
  RETURNS TRIGGER
  AS $$
  BEGIN
  IF TG_OP = 'UPDATE' AND OLD.PROJECTNO != NEW.PROJECTNO OR
  TG_OP = 'DELETE' THEN
  UPDATE EMP
    SET PROJECTNO = NULL
    WHERE EMP.PROJECTNO = OLD.PROJECTNO;
    END IF;

    IF TG_OP = 'UPDATE' THEN RETURN NULL;
      ELSIF TG_OP = 'DELETE' THEN RETURN NULL;
      END IF;
  END;
$$ LANGUAGE PLPGSQL;

创建触发器。

CREATE TRIGGER TRG_PROJECTS_SET_NULL
  AFTER UPDATE OF PROJECTNO OR DELETE
  ON PROJECTS
  FOR EACH ROW
  EXECUTE PROCEDURE PROJECTS_SET_NULL();

通过从 PROJECTS 表中删除一行来测试触发器。

DELETE FROM PROJECTS WHERE PROJECTNO=123;
SELECT PROJECTNO FROM EMP WHERE PROJECTNO=123;
projectno
(0 rows)

创建一个 DDL 触发器,该触发器是 Oracle DDL 系统/模式级别触发器的等效版本(例如阻止对 HR 模式中的对象运行 DDL DROP 的触发器)。

创建事件触发器函数。

请注意,创建触发器函数时不带参数,并且返回类型必须为TRIGGEREVENT_TRIGGER

CREATE OR REPLACE FUNCTION ABORT_DROP_COMMAND()
RETURNS EVENT_TRIGGER
AS $$
BEGIN
  RAISE EXCEPTION 'The % Command is Disabled', tg_tag;
END;
$$ LANGUAGE PLPGSQL;

创建事件触发器,该触发器将在 DDL DROP 命令开始之前触发。

CREATE EVENT TRIGGER trg_abort_drop_command
  ON DDL_COMMAND_START
  WHEN TAG IN ('DROP TABLE', 'DROP VIEW',
    'DROP FUNCTION', 'DROP SEQUENCE',
    'DROP MATERIALIZED VIEW', 'DROP TYPE')
  EXECUTE PROCEDURE abort_drop_command();

通过尝试删除 EMPLOYEE 表来测试触发器。

DROP TABLE EMPLOYEES;
ERROR: The DROP TABLE Command is Disabled
CONTEXT: PL/pgSQL function abort_drop_command() line 3 at RAISE

总结

触发 Oracle PostgreSQL
在更新触发器之前,行级别 CREATE OR REPLACE TRIGGER check_update
BEFORE UPDATE ON projects
FOR EACH ROW
BEGIN
/*Trigger body*/
END;
/
CREATE TRIGGER check_update
BEFORE UPDATE ON employees
FOR EACH ROW
EXECUTE PROCEDURE myproc();
在更新触发器之前,语句级别 CREATE OR REPLACE TRIGGER check_update
BEFORE UPDATE ON projects
BEGIN
/*Trigger body*/
END;
/
CREATE TRIGGER check_update
BEFORE UPDATE ON employees
FOR EACH STATEMENT
EXECUTE PROCEDURE myproc();
系统/事件触发器 CREATE OR REPLACE TRIGGER drop_trigger
BEFORE DROP ON hr.SCHEMA
BEGIN
RAISE_APPLICATION_ERROR (
num => -20000,
msg => 'Cannot drop object');
END;
/
CREATE EVENT TRIGGER trg_drops
ON ddl_command_start
EXECUTE PROCEDURE trg_drops();
在触发器中引用:old:new 在触发器主体中使用 “:NEW” 和 “:OLD”:
CREATE OR REPLACE TRIGGER Upper-NewDeleteOld
BEFORE INSERT OR UPDATE
OF first_name ON employees
FOR EACH ROW
BEGIN
:NEW.first_name := UPPER(:NEW.first_name);
:NEW.salary := :OLD.salary;
END;
/
在触发器过程主体中使用 “NEW“ 和 ”OLD“:
CREATE OR REPLACE FUNCTION log_emp_name_upd()
RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
IF NEW.last_name <> OLD.last_name
THEN
INSERT INTO employee_audit (employee_ id,last_name,changed_on) VALUES (OLD.id,OLD.last_name,now());
END IF;
RETURN NEW;
END; $$

CREATE TRIGGER last_name_change_trg
BEFORE UPDATE ON employees
FOR EACH ROW
EXECUTE PROCEDURE log_last_emp_name_upd();
数据库事件级别触发器 CREATE TRIGGER register_shutdown
ON DATABASE SHUTDOWN
BEGIN
Insert into logging values
('DB was shut down', sysdate);
commit;
END;
/
不适用
删除触发器 DROP TRIGGER last_name_change_trg; DROP TRIGGER last_name_change_trg on employees;
修改触发器运行的逻辑 可与创建或替换一起使用
CREATE OR REPLACE TRIGGER UpperNewDeleteOld
BEFORE INSERT OR UPDATE OF first_name ON employees
FOR EACH ROW
BEGIN
<<NEW CONTENT>>
END;
/
在触发器中调用的函数上使用创建或替换(触发器保持不变)
CREATE or replace FUNCTION UpperNewDeleteOld()
RETURNS trigger
AS $UpperNewDeleteOld$
BEGIN
<<NEW CONTENT>>
END;
$UpperNewDeleteOld$
LANGUAGE plpgsql;
启用触发器 ALTER TRIGGER UpperNewDeleteOld ENABLE; alter table employees enable trigger Upper-NewDeleteOld;
禁用触发器 ALTER TRIGGER UpperNewDeleteOld DISABLE; alter table employees disable trigger Upper-NewDeleteOld;

有关详细信息,请参阅 PostgreSQL 文档中的 CREATE TRIGGER触发函数