CodeGym /Các khóa học /SQL SELF /Các loại trigger trong PostgreSQL: BEFORE, AFTER, INSTEAD...

Các loại trigger trong PostgreSQL: BEFORE, AFTER, INSTEAD OF

SQL SELF
Mức độ, Bài học
Có sẵn

Trigger trong PostgreSQL được chia thành ba loại chính:

  1. BEFORE — chạy trước khi thực hiện thao tác chính (ví dụ, trước INSERT, UPDATE hoặc DELETE). Bạn có thể dùng để ngăn thao tác hoặc chỉnh sửa dữ liệu trước khi lưu.

  2. AFTER — chạy sau khi thao tác chính đã hoàn thành. Loại này thường dùng để ghi log, tạo bản ghi liên quan hoặc thực hiện các hành động phụ thuộc vào việc thao tác thành công.

  3. INSTEAD OF — chạy thay cho thao tác thực tế. Chỉ dùng cho view thôi nhé. Ví dụ, nếu user cố gắng insert vào view, bạn có thể kiểm soát quá trình này bằng trigger INSTEAD OF.

Trigger BEFORE

Trigger BEFORE được kích hoạt trước khi PostgreSQL thực hiện thao tác chính. Nó hữu ích nếu bạn muốn kiểm tra hoặc chỉnh sửa dữ liệu ngay trước khi lưu. Hãy tưởng tượng nó giống như kiểm tra hành lý trước khi lên máy bay: nếu hành lý không ổn, bạn có thể sửa hoặc chặn luôn.

Lấy ví dụ kiểm tra dữ liệu trước khi insert nhé. Giả sử có bảng students để lưu thông tin sinh viên. Mình muốn đảm bảo tuổi sinh viên không vượt quá 100 (khó tin thật, nhưng cứ thế đi).

Tạo bảng:

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

Tạo function cho trigger:

CREATE OR REPLACE FUNCTION validate_age()
RETURNS TRIGGER AS $$
BEGIN
    IF NEW.age > 100 THEN
        RAISE EXCEPTION 'Tuổi của sinh viên không thể lớn hơn 100!';
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

Tạo trigger:

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

Bây giờ, nếu bạn thử insert sinh viên có tuổi lớn hơn 100, PostgreSQL sẽ báo lỗi:

INSERT INTO students (name, age) VALUES ('Ivan Ivanov', 120);
-- Lỗi: Tuổi của sinh viên không thể lớn hơn 100!

Kiểm tra đơn giản vậy thôi!

Trigger AFTER

Trigger AFTER chạy sau khi thao tác chính đã thành công. Nó hữu ích để thực hiện các hành động phụ thuộc vào kết quả thao tác, ví dụ ghi log hoặc tạo bản ghi liên quan.

Kịch bản: có bảng students, mình muốn ghi lại mọi thay đổi vào bảng log riêng.

Tạo bảng log:

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

Tạo function cho trigger:

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 chứa loại thao tác: INSERT, UPDATE hoặc DELETE
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

Tạo trigger:

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

Bây giờ, khi bạn thêm sinh viên mới, PostgreSQL sẽ tự động ghi thao tác vào log:

INSERT INTO students (name, age) VALUES ('Anna Ling', 22);

SELECT * FROM students_log;
-- Kết quả:
-- id | student_id | operation |        timestamp
--  1 |          1 | INSERT    | 2023-11-15 12:00:00

Trigger INSTEAD OF

Trigger INSTEAD OF chạy thay cho thao tác thực tế. Đây là loại trigger duy nhất dùng được với view. Nó cho phép bạn xử lý linh hoạt các thao tác mà bình thường không thể thực hiện trực tiếp trên view.

Kịch bản: có hai bảng courses và teachers. Mình sẽ tạo view kết hợp hai bảng này và viết trigger để xử lý thao tác insert qua view đó.

Tạo bảng:

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
);

Tạo 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;

Vấn đề: bạn không thể insert trực tiếp vào view vì nó tổng hợp dữ liệu từ hai bảng. Giải pháp: dùng trigger INSTEAD OF.

Tạo function cho trigger:

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;  -- Dữ liệu không lưu trong view
END;
$$ LANGUAGE plpgsql;

Tạo trigger:

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

Bây giờ bạn có thể insert trực tiếp vào view:

INSERT INTO course_details (course_name, teacher_name)
VALUES ('Toán học', 'Alex Ming');

SELECT * FROM courses;
-- Kết quả:
-- id | name        | teacher_id
--  1 | Toán học    | 1

SELECT * FROM teachers;
-- Kết quả:
-- id | name
--  1 | Alex Ming

So sánh các loại trigger

Loại trigger Khi nào chạy Ứng dụng chính
BEFORE Trước khi thực hiện thao tác Kiểm tra, chuẩn bị dữ liệu
AFTER Sau khi hoàn thành thành công Ghi log, cập nhật dữ liệu liên quan
INSTEAD OF Thay cho thao tác thực tế Xử lý thao tác trên view

Đặc điểm và hạn chế

BEFORE trigger có thể chỉnh sửa dữ liệu trước khi thao tác. Ví dụ, bạn có thể tự động format tên (chuyển sang viết hoa chẳng hạn).

AFTER trigger không thể thay đổi dữ liệu vì thao tác đã xong rồi. Nó chỉ dùng cho các hành động tiếp theo thôi.

INSTEAD OF trigger chỉ dùng cho view. Nó cho phép bạn triển khai logic phức tạp khi insert/update dữ liệu vào nhiều bảng liên quan.

Vậy là xong cho hôm nay nhé! Nếu BEFORE, AFTER và INSTEAD OF thấy hơi rối thì cũng đừng lo. Quan trọng là nhớ nguyên tắc chính và các kịch bản sử dụng. Thử tự làm vài ví dụ để luyện tập cho chắc nha.

2
Nhiệm vụ
SQL SELF, mức độ, bài học
Đã khóa
Tạo trigger `BEFORE` để kiểm tra dữ liệu
Tạo trigger `BEFORE` để kiểm tra dữ liệu
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION