PostgreSQL里的触发器分三大类:
BEFORE—— 在主操作执行之前触发(比如在INSERT、UPDATE或DELETE之前)。你可以用它们来阻止操作或者在保存前修改数据。AFTER—— 在主操作完成后触发。这个类型常用来做日志、创建关联记录,或者做那些依赖操作成功的事。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)上的触发器类型。这样你就能灵活处理那些不能直接在视图上做的操作。
场景:我们有两个表courses和teachers。我们建个视图把它们合起来,然后写个触发器让你能通过视图插数据。
先建表:
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触发器只能用在视图上。它们让你能在多个相关表里实现复杂的数据插入/修改逻辑。
今天就到这啦!如果你觉得BEFORE、AFTER和INSTEAD OF有点难,别担心。记住它们的核心原理和用法场景就行。可以自己多写几个例子练练手,加深理解。
GO TO FULL VERSION