Trigger trong PostgreSQL không chỉ cho phép chạy function khi có thao tác nào đó, mà còn truyền vào function mấy biến siêu tiện. Nhờ mấy biến này, bạn biết được dữ liệu trong bảng trước khi thao tác là gì, sau khi thao tác ra sao, và thao tác nào vừa xảy ra luôn.
OLD— chứa dữ liệu cũ của dòng trong bảng trước khi thao tác. Dùng trong trigger choUPDATEvàDELETE, vì vớiINSERTthì làm gì có cái gì "cũ" đâu mà lấy.NEW— chứa dữ liệu mới của dòng trong bảng sau khi thao tác. Dùng trong trigger choINSERTvàUPDATE.TG_OP— chứa thông tin dạng text về thao tác hiện tại:INSERT,UPDATE, hoặcDELETE.
Tất cả mấy biến này tự động có sẵn trong function gắn với trigger luôn nhé.
Lý thuyết mà không thực hành thì như SQL mà không có index: chậm và buồn ngủ. Thôi cùng xem ví dụ thực tế cho dễ hiểu.
Dùng OLD để truy cập dữ liệu cũ
Giả sử bạn có bảng students. Và ai đó sửa tuổi của sinh viên (kiểu như nhầm, tưởng sinh viên mới 20 tuổi chứ không phải 25).
CREATE TABLE students (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
age INT NOT NULL
);
Để theo dõi xem thay đổi gì, mình tạo bảng log:
CREATE TABLE student_changes (
change_id SERIAL PRIMARY KEY,
student_id INT NOT NULL,
old_value INT,
new_value INT,
change_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Tiếp theo, tạo function để ghi lại thay đổi. Đây là lúc OLD phát huy tác dụng:
CREATE OR REPLACE FUNCTION log_student_changes()
RETURNS TRIGGER AS $$
BEGIN
-- Ghi log thay đổi tuổi
INSERT INTO student_changes (student_id, old_value, new_value)
VALUES (OLD.id, OLD.age, NEW.age);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Giờ tạo trigger luôn:
CREATE TRIGGER student_age_update
AFTER UPDATE OF age ON students
FOR EACH ROW
WHEN (OLD.age IS DISTINCT FROM NEW.age) -- Chỉ chạy nếu tuổi thay đổi
EXECUTE FUNCTION log_student_changes();
Thêm sinh viên rồi sửa thông tin thử nhé:
INSERT INTO students (name, age) VALUES ('Алиса', 20);
UPDATE students
SET age = 25
WHERE name = 'Алиса';
-- Kiểm tra log thay đổi:
SELECT * FROM student_changes;
Bạn sẽ thấy trong bảng log có ghi lại thay đổi: tuổi từ 20 thành 25. Ảo diệu không? Không đâu, là nhờ OLD đấy.
Dùng NEW cho dữ liệu mới
Giờ giả sử bạn muốn khi thêm sinh viên mới thì tự động ghi lại ID và tên vào bảng log (kiểu hơi paranoid, nhưng đôi khi cũng hữu ích):
CREATE OR REPLACE FUNCTION log_new_student()
RETURNS TRIGGER AS $$
BEGIN
-- Ghi log dữ liệu sinh viên mới
INSERT INTO student_changes (student_id, old_value, new_value)
VALUES (NEW.id, NULL, NEW.age); -- Không có giá trị cũ vì là INSERT
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER student_insert_log
AFTER INSERT ON students
FOR EACH ROW
EXECUTE FUNCTION log_new_student();
Thêm sinh viên mới và kiểm tra log luôn:
INSERT INTO students (name, age) VALUES ('Боб', 22);
-- Kiểm tra log:
SELECT * FROM student_changes;
Bạn sẽ thấy log có thêm sinh viên mới. Đúng là chăm dữ liệu level mới luôn!
Dùng TG_OP để xác định loại thao tác
Nhưng nếu bạn muốn có một trigger đa năng để log cả INSERT, UPDATE lẫn DELETE thì sao? Lúc này, biến TG_OP là cứu tinh.
Tạo function đa năng luôn:
CREATE OR REPLACE FUNCTION log_all_operations()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
INSERT INTO student_changes (student_id, old_value, new_value)
VALUES (NEW.id, NULL, NEW.age);
ELSIF TG_OP = 'UPDATE' THEN
INSERT INTO student_changes (student_id, old_value, new_value)
VALUES (OLD.id, OLD.age, NEW.age);
ELSIF TG_OP = 'DELETE' THEN
INSERT INTO student_changes (student_id, old_value, new_value)
VALUES (OLD.id, OLD.age, NULL);
END IF;
RETURN NULL; -- Với AFTER trigger trên DELETE thì trả về NULL
END;
$$ LANGUAGE plpgsql;
Tạo trigger cho cả ba thao tác luôn:
CREATE TRIGGER universal_student_log
AFTER INSERT OR UPDATE OR DELETE ON students
FOR EACH ROW
EXECUTE FUNCTION log_all_operations();
Thêm, sửa, xoá sinh viên thử nhé:
INSERT INTO students (name, age) VALUES ('Чарли', 30);
UPDATE students SET age = 31 WHERE name = 'Чарли';
DELETE FROM students WHERE name = 'Чарли';
-- Kiểm tra log:
SELECT * FROM student_changes;
Bạn sẽ thấy log đầy đủ mọi thao tác — một trigger cân hết luôn!
Lỗi thường gặp khi dùng OLD, NEW, TG_OP
Làm việc với trigger dễ gặp mấy lỗi phổ biến sau:
"Sao OLD không hoạt động khi insert?" Đây là mặc định: với INSERT thì không có dữ liệu cũ. Dùng NEW nhé.
"Làm gì khi NEW không hoạt động lúc xoá?" Cũng là hành vi chuẩn: với DELETE thì không có dữ liệu mới. Dùng OLD thôi.
Logic trigger gây đệ quy vô tận. Nhớ kiểm tra để trigger không tự gọi lại chính nó. Có thể dùng điều kiện rõ ràng trong block WHEN hoặc kiểm tra TG_OP nhé.
GO TO FULL VERSION