Триггеры в PostgreSQL делятся на три основных категории:
BEFORE— выполняются перед выполнением основной операции (например, передINSERT,UPDATEилиDELETE). Вы можете использовать их, чтобы предотвратить выполнение операции или модифицировать данные перед их сохранением.AFTER— выполняются после того, как основная операция завершена. Этот тип часто используется для логирования, создания связанных записей или выполнения действий, которые зависят от успешного завершения операции.INSTEAD OF— выполняются вместо фактической операции. Используются только для представлений. Например, если пользователь пытается вставить данные в представление, вы можете управлять этим процессом с помощью триггера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 срабатывают вместо выполнения операции. Это единственный тип триггеров, который можно использовать с представлениями (views). Они дают гибкость в обработке операций, которые нельзя выполнить напрямую на представлении.
Сценарий: у нас есть две таблицы 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 кажутся сложными, не переживайте. Главное — помнить их основной принцип и сценарии использования. Попробуйте реализовать несколько примеров самостоятельно, чтобы закрепить материал.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ