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 上。比如說,使用者想往 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 上做的操作。

情境:我們有兩個表 coursesteachers。我們會做個 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 上。它們讓你可以在多個關聯表裡實作複雜的插入/修改邏輯。

今天就到這邊啦!如果 BEFOREAFTERINSTEAD OF 讓你覺得有點複雜,別擔心。記住它們的基本原則和用法就好。自己動手做幾個例子,馬上就會上手了!

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION