在 PostgreSQL 裡,觸發器分成三大類:
BEFORE— 在主要操作執行之前觸發(像是INSERT、UPDATE或DELETE之前)。你可以用它來阻止操作發生,或是在資料存進去前先改一下內容。AFTER— 在主要操作完成之後觸發。這種通常拿來做紀錄、建立關聯資料,或是做一些要等操作成功後才能做的事。INSTEAD OF— 會取代實際操作來執行。這個只用在 view 上。比如說,使用者想往 view 裡插資料,你就可以用INSTEAD OF觸發器來控制這個流程。
BEFORE 觸發器
BEFORE 觸發器會在 PostgreSQL 執行主要操作之前先跑。這很適合你想在資料存進去前檢查或修改內容。想像一下,就像你上飛機前檢查行李:如果行李不合格,可以改一改或直接擋掉。
來個資料驗證的例子吧。假設我們有個 students 表格,專門存學生資訊。我們想確保學生年齡不會超過 100 歲(雖然很少見啦)。
先建立表格:
CREATE TABLE students (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
age INT NOT NULL
);
再來寫個觸發器用的 function:
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 觸發器會在主要操作成功完成後才跑。這很適合做一些要等操作結果出來才能做的事,比如紀錄 log 或建立關聯資料。
情境:我們有個 students 表格,想把所有異動都記到另一個 log 表裡。
先建立 log 表:
CREATE TABLE students_log (
id SERIAL PRIMARY KEY,
student_id INT,
operation TEXT,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
寫個 function 給觸發器用:
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 都會自動把操作記到 log:
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 上的觸發器。它讓你可以處理那些沒辦法直接在 view 上做的操作。
情境:我們有兩個表 courses 跟 teachers。我們會做個 view 把它們合起來,然後寫個觸發器讓你可以直接從這個 view 插資料。
先建立表格:
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
);
建立 view:
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;
問題來了:你不能直接往這個 view 插資料,因為它是從兩個表合起來的。解法就是用 INSTEAD OF 觸發器。
寫個 function 給觸發器用:
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; -- view 裡不會存資料
END;
$$ LANGUAGE plpgsql;
建立觸發器:
CREATE TRIGGER instead_of_insert_course_details
INSTEAD OF INSERT ON course_details
FOR EACH ROW
EXECUTE FUNCTION insert_course_details();
現在你可以直接往 view 插資料囉:
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 |
操作成功後 | 紀錄 log、更新關聯資料 |
INSTEAD OF |
取代操作執行 | 處理 view 上的操作 |
特色與限制
BEFORE 觸發器 可以在操作前修改資料。比如你可以自動把名字轉成大寫。
AFTER 觸發器 不能再改資料,因為操作已經完成。它們只適合做後續處理。
INSTEAD OF 觸發器 只能用在 view 上。它們讓你可以在多個關聯表裡實作複雜的插入/修改邏輯。
今天就到這邊啦!如果 BEFORE、AFTER 跟 INSTEAD OF 讓你覺得有點複雜,別擔心。記住它們的基本原則和用法就好。自己動手做幾個例子,馬上就會上手了!
GO TO FULL VERSION