Что такое triggers в PostgreSQL?
Что такое триггер в PostgreSQL
Триггер — это объект базы данных, связывающий событие (INSERT, UPDATE, DELETE, TRUNCATE) с функцией, которая автоматически выполняется при наступлении этого события. Триггер не вызывается явно — он срабатывает как реакция на изменение данных.
Каждый триггер состоит из двух частей:
- Триггерная функция — хранимая процедура, написанная на PL/pgSQL (или другом языке), возвращающая тип
TRIGGER. - Определение триггера — привязывает функцию к таблице, событию и моменту срабатывания.
Моменты срабатывания
BEFORE— триггер вызывается до применения изменения. Функция может модифицировать строку (NEW) или отменить операцию, вернувNULL.AFTER— вызывается после успешного применения изменения. Используется для логирования, отправки уведомлений.INSTEAD OF— применяется только к представлениям (VIEW), заменяет стандартную операцию.
Уровни срабатывания
FOR EACH ROW— функция вызывается отдельно для каждой затронутой строки. Внутри доступныOLD(прежнее значение) иNEW(новое значение).FOR EACH STATEMENT— функция вызывается один раз на весь оператор, независимо от числа затронутых строк.
Специальные переменные в триггерной функции
NEW— новая строка (доступна при INSERT и UPDATE).OLD— старая строка (доступна при UPDATE и DELETE).TG_OP— строка с названием операции:'INSERT','UPDATE','DELETE'.TG_TABLE_NAME— имя таблицы, на которой сработал триггер.
Пример: аудит изменений
-- Создаём таблицу аудита
CREATE TABLE users_audit (
id SERIAL PRIMARY KEY,
user_id INT,
old_email TEXT,
new_email TEXT,
changed_at TIMESTAMPTZ DEFAULT now()
);
-- Триггерная функция
CREATE OR REPLACE FUNCTION log_user_email_change()
RETURNS TRIGGER AS $$
BEGIN
-- Пишем запись только если email изменился
IF OLD.email IS DISTINCT FROM NEW.email THEN
INSERT INTO users_audit(user_id, old_email, new_email)
VALUES (OLD.id, OLD.email, NEW.email);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Привязываем функцию к таблице
CREATE TRIGGER trg_user_email_audit
AFTER UPDATE ON users
FOR EACH ROW
EXECUTE FUNCTION log_user_email_change();
Пример: автоматическое заполнение поля
-- Триггер устанавливает updated_at перед каждым обновлением
CREATE OR REPLACE FUNCTION set_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW; -- возвращаем изменённую строку
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_set_updated_at
BEFORE UPDATE ON products
FOR EACH ROW
EXECUTE FUNCTION set_updated_at();
Управление триггерами
-- Временно отключить триггер
ALTER TABLE users DISABLE TRIGGER trg_user_email_audit;
-- Включить обратно
ALTER TABLE users ENABLE TRIGGER trg_user_email_audit;
-- Удалить триггер
DROP TRIGGER trg_user_email_audit ON users;
Когда использовать триггеры
Триггеры подходят для: автоматического аудита, поддержания денормализованных агрегатов, валидации сложных ограничений, обновления updated_at. Их следует избегать для сложной бизнес-логики, которую лучше реализовать на уровне приложения — это упрощает отладку и тестирование.
Что хочет услышать интервьюер
Понимание разницы между BEFORE и AFTER триггерами и когда применять каждый из них
Знание переменных NEW и OLD и их доступности в зависимости от типа операции
Понимание разницы между FOR EACH ROW и FOR EACH STATEMENT
Умение написать триггерную функцию на PL/pgSQL и привязать её к таблице
Осознание побочных эффектов: влияние на производительность, сложность отладки, скрытая логика
Пример: AFTER-триггер: аудит изменения email
-- Триггерная функция для автоматического аудита email
CREATE OR REPLACE FUNCTION log_user_email_change()
RETURNS TRIGGER AS $$
BEGIN
IF OLD.email IS DISTINCT FROM NEW.email THEN
INSERT INTO users_audit(user_id, old_email, new_email)
VALUES (OLD.id, OLD.email, NEW.email);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_user_email_audit
AFTER UPDATE ON users
FOR EACH ROW
EXECUTE FUNCTION log_user_email_change();
Пример: BEFORE-триггер: автообновление updated_at
-- BEFORE-триггер автоматически проставляет updated_at
CREATE OR REPLACE FUNCTION set_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_set_updated_at
BEFORE UPDATE ON products
FOR EACH ROW
EXECUTE FUNCTION set_updated_at();
Типичные ошибки
Путают момент срабатывания BEFORE и AFTER: возвращают NEW в AFTER-триггере, хотя это уже не влияет на строку
Забывают, что в DELETE-триггере переменная NEW недоступна — обращение к ней вызывает ошибку
Не учитывают рекурсивный вызов: триггер на UPDATE изнутри снова модифицирует таблицу и запускает сам себя
Используют FOR EACH STATEMENT, ожидая доступа к NEW/OLD, хотя на уровне оператора эти переменные недоступны
Перегружают триггеры бизнес-логикой, делая систему непредсказуемой и трудной для тестирования


