CodeGym /课程 /SQL SELF /PostgreSQL里的触发器类型:BEFORE、AFTER、INSTEAD OF

PostgreSQL里的触发器类型:BEFORE、AFTER、INSTEAD OF

SQL SELF
第 57 级 , 课程 1
可用

PostgreSQL里的触发器分三大类:

  1. BEFORE —— 在主操作执行之前触发(比如在INSERTUPDATEDELETE之前)。你可以用它们来阻止操作或者在保存前修改数据。

  2. AFTER —— 在主操作完成后触发。这个类型常用来做日志、创建关联记录,或者做那些依赖操作成功的事。

  3. INSTEAD OF —— 替代实际操作来执行。只用在视图(view)上。比如用户想往视图插数据,你可以用INSTEAD OF触发器来控制整个过程。

BEFORE触发器

BEFORE触发器会在PostgreSQL执行主操作前触发。如果你想在保存前检查或改数据,这就很有用。想象一下,就像你上飞机前安检行李:不合格的行李可以改,也可以直接拦下来。

举个插入前校验数据的例子。 假设我们有个students表,存学生信息。我们想保证学生年龄不能超过100岁(虽然不太可能啦)。

先建表:

CREATE TABLE students (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    age INT NOT NULL
);

写个触发器函数:

CREATE OR REPLACE FUNCTION validate_age()
RETURNS TRIGGER AS $$
BEGIN
    IF NEW.age > 100 THEN
        RAISE EXCEPTION '学生年龄不能超过100岁!';
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

创建触发器:

CREATE TRIGGER before_insert_students
BEFORE INSERT ON students
FOR EACH ROW
EXECUTE FUNCTION validate_age();

现在,如果你想插入一个年龄大于100的学生,PostgreSQL会报错:

INSERT INTO students (name, age) VALUES ('伊万 伊万诺夫', 120);
-- 错误:学生年龄不能超过100岁!

就是这么简单的校验!

AFTER触发器

AFTER触发器会在主操作成功后触发。适合做那些依赖操作结果的事,比如写日志或者创建关联记录。

场景:我们有个students表,想把所有变更都记录到一个日志表里。

先建日志表:

CREATE TABLE students_log (
    id SERIAL PRIMARY KEY,
    student_id INT,
    operation TEXT,
    timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

写个触发器函数:

CREATE OR REPLACE FUNCTION log_student_changes()
RETURNS TRIGGER AS $$
BEGIN
    INSERT INTO students_log (student_id, operation)
    VALUES (NEW.id, TG_OP);  -- TG_OP里是操作类型:INSERT、UPDATE或DELETE
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

创建触发器:

CREATE TRIGGER after_insert_students
AFTER INSERT ON students
FOR EACH ROW
EXECUTE FUNCTION log_student_changes();

现在,每次你加新学生,PostgreSQL会自动把操作写进日志:

INSERT INTO students (name, age) VALUES ('安娜 林', 22);

SELECT * FROM students_log;
-- 结果:
-- id | student_id | operation |        timestamp
--  1 |          1 | INSERT    | 2023-11-15 12:00:00

INSTEAD OF触发器

INSTEAD OF触发器会替代原本的操作来执行。它是唯一能用在视图(view)上的触发器类型。这样你就能灵活处理那些不能直接在视图上做的操作。

场景:我们有两个表coursesteachers。我们建个视图把它们合起来,然后写个触发器让你能通过视图插数据。

先建表:

CREATE TABLE courses (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    teacher_id INT NOT NULL
);

CREATE TABLE teachers (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL
);

建视图:

CREATE VIEW course_details AS
SELECT 
    courses.id AS course_id,
    courses.name AS course_name,
    teachers.name AS teacher_name
FROM courses
JOIN teachers ON courses.teacher_id = teachers.id;

问题:你不能直接往视图插数据,因为它聚合了两个表的数据。解决办法:用INSTEAD OF触发器。

写个触发器函数:

CREATE OR REPLACE FUNCTION insert_course_details()
RETURNS TRIGGER AS $$
BEGIN
    INSERT INTO teachers (name) VALUES (NEW.teacher_name) RETURNING id INTO NEW.teacher_id;
    INSERT INTO courses (name, teacher_id) VALUES (NEW.course_name, NEW.teacher_id);
    RETURN NULL;  -- 视图里不保存数据
END;
$$ LANGUAGE plpgsql;

创建触发器:

CREATE TRIGGER instead_of_insert_course_details
INSTEAD OF INSERT ON course_details
FOR EACH ROW
EXECUTE FUNCTION insert_course_details();

现在你可以直接往视图插数据了:

INSERT INTO course_details (course_name, teacher_name)
VALUES ('数学', '阿历克斯 明');

SELECT * FROM courses;
-- 结果:
-- id | name        | teacher_id
--  1 | 数学        | 1

SELECT * FROM teachers;
-- 结果:
-- id | name
--  1 | 阿历克斯 明

触发器类型对比

触发器类型 什么时候执行 主要用途
BEFORE 操作执行前 校验、准备数据
AFTER 操作成功后 日志、更新关联数据
INSTEAD OF 替代操作执行 处理视图上的操作

特点和限制

BEFORE触发器可以在操作前修改数据。比如你可以自动把名字转成大写。

AFTER触发器不能改数据,因为操作已经完成了。它们只适合做后续动作。

INSTEAD OF触发器只能用在视图上。它们让你能在多个相关表里实现复杂的数据插入/修改逻辑。

今天就到这啦!如果你觉得BEFOREAFTERINSTEAD OF有点难,别担心。记住它们的核心原理和用法场景就行。可以自己多写几个例子练练手,加深理解。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION