Раздел помощи SpaceWeb

PostgreSQL-триггеры: создание, удаление, примеры

08 авг, 2024

Триггер в PostgreSQL – это специальный механизм, который позволяет автоматически выполнять конкретную операцию при наступлении некоторого события в базе данных. 

Он состоит из двух основных компонентов: условия, которое определяет, когда триггер должен сработать, и действия, которое должно быть выполнено, когда триггер срабатывает.

Уникальная особенность триггеров в PostgreSQL – это возможность использовать встроенный процедурный язык PL/pgSQL. Он помогает создавать очень мощные и гибкие триггеры. Например, можно не только изменять данные в других таблицах, но и выполнять сложные вычисления, журналировать изменения или вызывать внешние скрипты

Типы триггеров

Триггеры в PostgreSQL активируются при выполнении команд INSERT, UPDATE или DELETE. В зависимости от времени их выполнения относительно события, триггеры можно разделить на:

  • До выполнения команды (BEFORE). Триггер срабатывает перед тем, как соответствующая операция будет выполнена. Это позволяет проверить или изменить данные до их внесения в таблицу. Например, триггер BEFORE можно использовать для проверки целостности данных или преобразования значений перед их сохранением. Такой подход позволяет предотвратить внесение некорректных данных и обеспечивает дополнительный уровень контроля над изменениями.
  • После выполнения команды (AFTER). Триггер активируется после того, как операция завершена. Этот тип триггеров полезен для действий, которые должны быть выполнены после внесения изменений: например, логирования или обновления агрегированных данных. AFTER-триггеры можно использовать для автоматического создания записей в журнале аудита или обновления связанных таблиц после изменения данных в основной таблице.
  • Вместо выполнения команды (INSTEAD OF). Используется в основном с представлениями (VIEW) и позволяет определить альтернативные действия, которые должны быть выполнены вместо стандартных операций INSERT, UPDATE или DELETE. Это позволяет создавать сложные представления, которые могут обрабатывать изменения данных в более гибкой форме. Например, триггер INSTEAD OF может направлять изменения в несколько базовых таблиц, тем самым поддерживая целостность данных.

На практике триггеры чаще всего делят на следующие типы:

  • Для каждой строки (FOR EACH ROW). Триггер срабатывает для каждой строки, которую затрагивает операция. 
  • Для каждой операции (FOR EACH STATEMENT). Триггер выполняется один раз для всего SQL-оператора, независимо от количества затронутых строк. 

Использование триггеров

Триггеры помогают создать сложную логику на стороне сервера – это может значительно улучшить производительность и надежность приложений.

Пример использования триггеров – автоматическое обновление столбца с временной меткой в таблице при каждом изменении записи. Такой триггер можно создать с использованием функции, которая обновляет соответствующий столбец значением текущего времени. Он позволяет легко отслеживать время последнего изменения записи без необходимости постоянно обновлять временную метку в каждом SQL-запросе.

Стоит отметить, что чрезмерное использование триггеров может привести к сложностям в отладке и поддержке кода, а также к снижению производительности при обработке больших объемов данных. Поэтому важно тщательно продумывать архитектуру системы и использовать триггеры там, где их применение действительно оправдано и приносит пользу.

Создание триггера  

Процесс создания триггера начинается с использования команды CREATE TRIGGER. Общий синтаксис выглядит так:

CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER имя_триггера { BEFORE | AFTER | INSTEAD OF } { событие [ OR ... ] }
    ON имя_таблицы
    [ FROM имя_ссылаемой_таблицы ]
    [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
    [ REFERENCING { { OLD | NEW } TABLE [ AS ] имя_переходной_таблицы } [ ... ] ]
    [ FOR [ EACH ] { ROW | STATEMENT } ]
    [ WHEN ( условие ) ]
    EXECUTE { FUNCTION | PROCEDURE } имя_функции ( аргументы )

Событиями, на которые может реагировать триггер, могут быть следующие действия:

  • INSERT (вставить);
  • UPDATE [ OF имя_столбца [, ... ] ] (обновить);
  • DELETE (удалить);
  • TRUNCATE (удалить все строки из набора таблиц).

Теперь рассмотрим основные моменты создания триггера.

  1. Во-первых, вы можете создать новый триггер или заменить уже существующий, используя ключевые слова CREATE OR REPLACE. Они довольно полезны, особенно если необходимо обновить логику существующего триггера без его предварительного удаления.
  2. Во-вторых, триггер всегда ассоциируется с конкретной таблицей, представлением или внешней таблицей. А значит, триггер будет срабатывать только при наступлении указанного события. Например, можно настроить триггер, чтобы он выполнялся перед вставкой (BEFORE INSERT) в определённую таблицу.
  3. Триггеры с опцией INSTEAD OF, которые предназначены для представлений, должны быть определены как FOR EACH ROW. Они заменяют стандартное поведение вставки, обновления или удаления строк в представлении. В свою очередь, триггеры, которые выполняются до (BEFORE) или после (AFTER) события в представлении, должны быть помечены как FOR EACH STATEMENT.
  4. Также можно задать условия, при которых триггер будет срабатывать. Для этого используется ключевое слово WHEN, за которым следует условие, которое должно быть истинным для выполнения триггера. Например, можно настроить триггер так, чтобы он срабатывал только при изменении определенного столбца.
  5. Функция или процедура, которую выполняет триггер, задаётся с помощью ключевых слов EXECUTE FUNCTION или EXECUTE PROCEDURE. Она получает доступ к старым и новым значениям данных через псевдонимы OLD и NEW, что позволяет реализовывать сложную логику обработки данных.

Примеры триггеров

Давайте рассмотрим несколько примеров, чтобы лучше понять, как работает триггер:

CREATE TRIGGER update_users_updated_at
BEFORE UPDATE ON users
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();

Триггер update_users_updated_at срабатывает перед каждым обновлением строки в таблице users и вызывает функцию update_updated_at_column. Он гарантирует, что поле updated_at будет автоматически обновляться при каждом изменении записи.

Если вам нужен триггер, который будет срабатывать перед каждой попыткой удаления строки из таблицы products и вызывать функцию prevent_deletion, вот такой пример:

CREATE TRIGGER prevent_products_deletion
BEFORE DELETE ON products
FOR EACH ROW
EXECUTE FUNCTION prevent_deletion()

Еще один пример – триггер log_users_changes, который срабатывает после каждой операции вставки, обновления или удаления строки в таблице users и вызывает функцию log_changes:

CREATE TRIGGER log_users_changes
AFTER INSERT OR UPDATE OR DELETE ON users
FOR EACH ROW
EXECUTE FUNCTION log_changes();

Он позволяет логировать все изменения в таблице users, записывая их в таблицу audit_log.

А вот триггер с условием: он будет срабатывать перед обновлением строки в таблице users, но только если поле email изменилось.

CREATE TRIGGER when_email_changes
BEFORE UPDATE ON users
FOR EACH ROW
WHEN (OLD.email IS DISTINCT FROM NEW.email)
EXECUTE FUNCTION update_updated_at_if_email_changed();

Если условие выполняется, вызывается функция update_updated_at_if_email_changed, которая обновляет поле updated_at.

Изменение триггера 

Чтобы изменить существующий триггер в PostgreSQL, воспользуйтесь командой CREATE OR REPLACE TRIGGER и укажите имя нужного триггера, связанную таблицу и новые параметры, которые хотите изменить. 

Кроме того, триггер можно переименовать с помощью команды ALTER TRIGGER. Синтаксис выглядит следующим образом:

ALTER TRIGGER старое_имя ON имя_таблицы RENAME TO новое_имя

Удаление триггера

Чтобы удалить триггер в PostgreSQL, нужна команда DROP TRIGGER. Она позволяет полностью удалить триггер из таблицы или представления.

Общий синтаксис:

DROP TRIGGER имя_триггера ON имя_таблицы;

Для большей гибкости и безопасности можно использовать дополнительные параметры при удалении триггера:

  • IF EXISTS позволяет избежать ошибки, если указанный триггер не существует. 
  • CASCADE автоматически удаляет все объекты, которые зависят от триггера, а также объекты, зависящие от этих объектов. 
  • RESTRICT предотвращает удаление триггера, если от него зависят другие объекты. 

Например, удалим триггер example_trigger из таблицы mytable:

DROP TRIGGER IF EXISTS example_ trigger ON mytable CASCADE;

Даже если триггер не существует, эта команда не вызовет ошибку благодаря параметру IF EXISTS. А CASCADE удалит все зависимые объекты.

Если необходимо не удалить, а временно отключить триггер, можно использовать команду ALTER TABLE

ALTER TABLE имя_таблицы DISABLE TRIGGER имя_триггера;

Чтобы обратно включить триггер, пропишите:

ALTER TABLE имя_таблицы ENABLE TRIGGER имя_триггера;


Триггерная функция

В PostgreSQL срабатывание триггера инициирует выполнение триггерной функции, которую необходимо создать до самого триггера. Для создания триггерной функции можно использовать различные процедурные языки, включая PL/pgSQL и даже С. Однако чистый SQL для создания триггерных функций не поддерживается, хотя PL/pgSQL и предоставляет аналогичную функциональность.

Базовый синтаксис для создания триггерной функции:

CREATE FUNCTION имя_функции()
RETURNS trigger AS
$$
BEGIN
    тело функции;
    RETURN [NEW|OLD|NULL];
END;
$$
LANGUAGE plpgsql;

Триггерная функция должна возвращать тип TRIGGER и объявляться без аргументов. Внутри функции можно использовать переменные NEW и OLD, которые содержат значения строк после и до выполнения операции соответственно:

  • Переменная NEW содержит новое значение строки после выполнения операции (например, при вставке или обновлении).
  • Переменная OLD содержит старое значение строки до выполнения операции (например, при обновлении или удалении).

Триггерные функции, которые вызываются для операторов с параметром FOR EACH STATEMENT, должны возвращать NULL. Функции, которые работают со строками, могут возвращать аргументы NULL, OLD или NEW, в зависимости от логики и типа операции:

  • RETURN NULL. Если функции выполняются после операции (AFTER), возвращаемое значение игнорируется. Если функции выполняются до операции (BEFORE), они возвращают NULL и пропускают обработку текущей строки, не прерывая операцию.
  • RETURN OLD. Функции до (BEFORE) DELETE, возвращают исходную строку OLD, тем самым позволяя выполнить операцию удаления со строкой. Если ее по какой-то причине нужно удалить, возвращается NULL.
  • RETURN NEW. Триггерные функции, которые выполняются перед операциями INSERT или UPDATE, возвращают новую или обновленную строку NEW, позволяя вставить или обновить строку. 

Использование аргументов при создании триггера позволяет одной функции обслуживать несколько триггеров, что делает код более модульным и упрощает его поддержку.

Пример триггерной функции:

CREATE FUNCTION age_check() RETURNS trigger AS $age_check$
BEGIN
    IF NEW.age IS NULL THEN
        RAISE EXCEPTION '% Нужно указать возраст', NEW.name;
    END IF;
    IF NEW.age < 18 THEN
        RAISE EXCEPTION '% Возраст должен быть не менее 18 лет', NEW.name;
    END IF;
    RETURN NEW;
END;
$age_check$ LANGUAGE plpgsql;

Она проверяет поле age (возраст) в новой записи таблицы. Если возраст не указан (NULL) или меньше 18 лет, она генерирует исключение с соответствующим сообщением об ошибке.

Зачем нужны триггеры

Триггеры в PostgreSQL предоставляют широкие возможности для автоматизации и управления данными на уровне базы данных. Они особенно важны для ситуаций, когда требуется автоматическое выполнение определенных действий при изменении данных. Триггеры стали незаменимы в приложениях, где важна поддержка целостности данных, автоматизация сложных процессов и обеспечение безопасности. 

Рассмотрим сценарии, в которых они могут быть полезны:

  1. Поддержание целостности данных. Триггеры могут гарантировать соблюдение сложных бизнес-правил и ограничений, которые сложно реализовать с помощью стандартного функционала базы данных. Например, они могут проверять наличие связанных записей в других таблицах или контролировать, чтобы значения определенных полей соответствовали установленным критериям.
  2. Автоматическое обновление связанных таблиц. Часто при изменении данных в одной таблице необходимо автоматически обновлять данные в связанных таблицах. Триггеры могут выполнять такие обновления, обеспечивая согласованность данных. Например, изменение цены товара в одной таблице может автоматически пересчитывать общую стоимость заказов в другой таблице.
  3. Логирование изменений. Ведение журнала изменений данных важно для аудита и отслеживания истории изменений. Триггеры могут автоматически создавать записи в журнале при вставке, обновлении или удалении данных, фиксируя информацию о том, кто и когда внёс изменения. 
  4. Реализация сложной бизнес-логики. Некоторые бизнес-правила требуют выполнения сложных вычислений или проверок, которые проще и эффективнее реализовать на уровне базы данных. Триггеры позволяют автоматически выполнять такие операции при изменении данных, уменьшая нагрузку на приложение и обеспечивая более надёжное выполнение логики.
  5. Поддержка вычисляемых полей. В некоторых случаях значения полей должны автоматически рассчитываться на основе других полей. Триггеры могут выполнять такие вычисления и обновлять значения полей при изменении данных. Например, при добавлении товара в заказ они автоматически вычислят и обновят общую сумму заказа.
  6. Обеспечение безопасности данных. Триггеры могут проверять права доступа и ограничивать операции на уровне базы данных. Например, можно создать триггеры, которые предотвращают удаление критически важных записей или ограничивают доступ к определенным данным для выбранных пользователей.

Заключение

Триггеры в PostgreSQL – это мощный инструмент, который позволяет автоматизировать и управлять сложными процессами на уровне базы данных. Они позволяют реализовывать сложные бизнес-правила, поддерживать целостность данных, автоматически обновлять связанные таблиц и логировать изменения. 

Если правильно использовать триггеры, они смогут существенно повысить надежность, безопасность и эффективность работы вашей базы данных.