При работе с триггерами, особенно в случаях, когда на уровне триггеров производятся каскадные или перекрестные обновления, возникает потребность отключать или производить определенные действия в триггерах прямо на уровне запроса.
Далее я хотел написать (и уже написал, но потом убрал) множество примеров, но так как они захватывают достаточно много сопутсвующих вещей и задач которые тоже потребуется разъяснять, что в конце я решил, что имеет смысл сгруппировать примеры по видам задач и сделать отдельные разборы, здесь же мы просто опишем основные правила и задачи управляющего поля.
Задачи управляющего поля
Обычное у меня название этого поля — _trigger_off. Изначально оно предназначалось для того, что бе когда производится INSERT или UPDATE, то если в запросе оно передается как TRUE, то в триггере происходил сразу возврат из процедуры с обнулением этого поля, то есть по сути триггерная функция вызывалась, но никаких действий не производила. В данный момент понятие этого поля трансформировалось в более широкое понятие, но название осталось прежним, так что связывать название этого поля с его значение в прямом смысле не имеет значения, вы можете его называть как вам удобно, я называю так, по привычке.
Тип поля — BOOLEAN. На самом деле, при условии что поле может быть NULL, то вариантов значений поля становится не 2 (TRUE, FALSE), а 4 (TRUE, FALSE, NULL, NOT NULL), это важно и вот почему:
- Данное поле позволяет определить стоит ли исполнять основной код триггерной функции или нет (TRUE или FALSE);
- Так же оно позволяет определить откуда был произведен запрос, из приложения или из соседнего триггера (NULL или NOT NULL);
В сухом остатке задачи управляющего поля можно определить таким образом: понять откуда пришел запрос и что с ним делать. И что бы на эти вопросы был однозначный ответ, требуется придерживаться определенных правил.
Правила управляющего поля
1. Поле в таблице ВСЕГДА сохранятся NULL
То есть, какое из значений поля не передавалось бы, в результате оно обязательно приводится к NULL. При чем, не зависимо от того, все ли триггера есть или только на определенные запросы, требуется ставить триггер-заглушку, которая сбросит значение поля в NULL. Для удобства я делаю одну общую хранимую процедуру:
CREATE FUNCTION "public"."flush_trigger_off" (
)
RETURNS TRIGGER AS
$body$
BEGIN
NEW._trigger_off := NULL;
RETURN NEW;
END;
$body$
LANGUAGE 'plpgsql'
VOLATILE;
А после создания таблицы в которой присутсвует управляющее поле, сразу создаю для нее триггер:
CREATE TRIGGER flush_control_field
BEFORE INSERT OR UPDATE
ON [table_name] FOR EACH ROW
EXECUTE PROCEDURE "public"."flush_trigger_off"();
И тогда не надо будет переживать по поводу того, что триггеры еще не написаны, но данные уже льются, и мало ли.
Соответсвенно при соблюдении данного правила, значение данного поля по-умочанию будет NULL, как в новой (NEW) так и в старой (OLD) строке.
Так же это правило, как следствие экономит место, так так поле всегда NULL, то места физически оно не занимает, только виртуально в разметке строк.
2. Данное поле НЕ должно использоваться внешним приложением
Так как данное поле является внутренним, то приложению, которое использует базу данных совершенно не обязательно о нем знать, а тем более использовать. Тут все просто, если приложение штатно использует данное поле, значит вы не справились с задачами целостности данных на уровне базы данных, и стоит пересмотреть всю логику.
Единственным исключением, когда данное поле можно использовать в запросах вне триггеров — это для восстановления целостности данных, что периодически бывает при достаточно большой и сложной структуре и повсеместном использовании денормализации. Но это не является штатными задачами приложения, так что правило здесь не работает. Но стоит иметь ввиду, что если вы используете это поле в «ручных» запросах, то контроль целостности данных и связей лежит исключительно на вас.
По большому счету значение поля NULL говорит о том, что триггер должен сам озаботится о целостности данных и решить что и как делать.
3. Данное поле должно ВСЕГДА использоваться во внутренних запросах
Это правило обратно предыдущему и подразумевает, что запрос вызван из какой либо хранимой процедуры и является внутренним, и соответвенно процедура уровнем выше понимает что делает и сама заботится о каскадных обновлениях либо наоборот указывает, что каскадные обновления должны производится на текущем уровне.
Здесь, конечно стоит разделять запросы обеспечения целостности данных и связей от обычных запросов, которые не влияют на целостность. Поэтому устанавливать принудительно это поле отличное от нуля для абсолютно всех подзапросов на уровне базы данных — не имеет смысла.
В итоге
Использование подобного поля позволяет контролировать точки входа запросов и принимать соответсвующие решения.