36.1. Обзор поведения триггеров#
36.1. Обзор поведения триггеров #
Триггер - это спецификация, согласно которой база данных должна автоматически выполнять определенную функцию при выполнении определенного типа операции. Триггеры могут быть привязаны к таблицам (секционированным или нет), представлениям и внешним таблицам.
На таблицах и внешних таблицах можно определить триггеры для выполнения операций INSERT
, UPDATE
или DELETE
либо перед, либо после операции, либо один раз для каждой измененной строки, либо один раз для SQL оператора. Триггеры UPDATE
могут также быть настроены на срабатывание только в случае, если определенные столбцы указаны в SET
части оператора UPDATE
. Триггеры также могут срабатывать для операторов TRUNCATE
. Если происходит событие триггера, вызывается триггерная функция в нужное время для обработки события.
На представлениях можно определить триггеры для выполнения вместо операций INSERT
, UPDATE
или DELETE
. Такие триггеры INSTEAD OF
срабатывают один раз для каждой строки, которую необходимо изменить в представлении. Обязанностью триггерной функции является выполнение необходимых изменений в базовой таблице(ах) представления и, при необходимости, возврат измененной строки в виде, в котором она будет отображаться в представлении. Триггеры на представлениях также могут быть определены для выполнения один раз для каждого SQL оператора, перед или после операций INSERT
, UPDATE
или DELETE
. Однако такие триггеры срабатывают только в том случае, если на представлении также определен триггер INSTEAD OF
. В противном случае любой оператор, направленный на представление, должен быть переписан в оператор, воздействующий на его базовую таблицу(ы), и затем триггеры, которые будут срабатывать, будут привязаны к базовой таблице(ам).
Триггерная функция должна быть определена до создания самого триггера. Триггерная функция должна быть объявлена как функция, не принимающая аргументов и возвращающая тип trigger
. (Триггерная функция получает свои входные данные через специально передаваемую структуру TriggerData
, а не в виде обычных аргументов функции).
После создания подходящей триггерной функции, триггер устанавливается с помощью CREATE TRIGGER. Одну и ту же триггерную функцию можно использовать для нескольких триггеров.
Tantor BE предлагает как триггеры на строку, так и триггеры на операцию. С триггером на строку, триггерная функция вызывается один раз для каждой строки, которая затрагивается операцией, вызвавшей триггер. В отличие от этого, триггер на операцию вызывается только один раз при выполнении соответствующей операции, независимо от количества строк, затронутых этой операцией. В частности, операция, которая не затрагивает ни одной строки, все равно приведет к выполнению всех применимых триггеров на операцию. Эти два типа триггеров иногда называются триггеры на уровне строки и триггеры на уровне операции соответственно. Триггеры на TRUNCATE
могут быть определены только на уровне операции, а не на уровне строки.
Триггеры также классифицируются в зависимости от того, срабатывают ли они перед, после или вместо операции. Они называются соответственно триггерами BEFORE
, AFTER
и INSTEAD OF
.
Триггеры BEFORE
на уровне оператора срабатывают перед началом выполнения оператора, а триггеры AFTER
на уровне оператора срабатывают в самом конце оператора. Эти типы триггеров могут быть определены для таблиц, представлений или внешних таблиц.
Триггеры BEFORE
на уровне строки срабатывают непосредственно перед выполнением операции над определенной строкой, а триггеры AFTER
на уровне строки срабатывают в конце оператора (но перед любыми триггерами AFTER
на уровне оператора). Эти типы триггеров могут быть определены только для таблиц и внешних таблиц, но не для представлений.
Триггеры INSTEAD OF
могут быть определены только для представлений и только на уровне строки; они срабатывают непосредственно при идентификации каждой строки в представлении, которая должна быть обработана.
Выполнение триггера AFTER
может быть отложено до конца транзакции, а не до конца оператора, если он был определен как триггер-ограничение. Во всех случаях триггер выполняется в рамках той же транзакции, что и оператор, вызвавший его, поэтому если либо оператор, либо триггер вызывают ошибку, изменения обоих будут отменены.
Выполнение оператора, который нацелен на родительскую таблицу в иерархии наследования или секционирования, не приводит к запуску триггеров на уровне оператора для затронутых дочерних таблиц; запускаются только триггеры на уровне оператора родительской таблицы. Однако, будут запущены триггеры на уровне строки для любых затронутых дочерних таблиц.
Если INSERT
содержит фразу ON CONFLICT DO UPDATE
, то возможно, что эффекты триггеров BEFORE
INSERT
на уровне строки и триггеров BEFORE
UPDATE
на уровне строки могут быть применены таким образом, что это будет видно из конечного состояния обновленной строки, если есть ссылка на столбец EXCLUDED
. Для выполнения обоих наборов триггеров на уровне строки не обязательно иметь ссылку на столбец EXCLUDED
, однако следует учитывать возможность неожиданных результатов, когда есть и триггеры BEFORE
INSERT
, и триггеры BEFORE
UPDATE
, которые изменяют вставляемую/обновляемую строку (это может быть проблематично, даже если модификации более или менее эквивалентны, если они не являются идемпотентными). Обратите внимание, что триггеры UPDATE
на уровне оператора выполняются при указании фразы ON CONFLICT DO UPDATE
, независимо от того, были ли какие-либо строки затронуты UPDATE
(и независимо от того, был ли выбран альтернативный путь UPDATE
). INSERT
с фразой ON CONFLICT DO UPDATE
будет выполнять триггеры INSERT
на уровне оператора BEFORE
сначала, затем триггеры UPDATE
на уровне оператора BEFORE
, за которыми следуют триггеры UPDATE
на уровне оператора AFTER
и, наконец, триггеры INSERT
на уровне оператора AFTER
.
Если UPDATE
на секционированной таблице приводит к перемещению строки
в другой раздел, это будет выполнено как DELETE
из исходной секции, за которой следует INSERT
в
новую секцию. В этом случае все триггеры BEFORE
UPDATE
на уровне строки и все триггеры на уровне строки
BEFORE
DELETE
будут запущены на
исходном разделе. Затем все триггеры BEFORE
INSERT
на уровне строки будут запущены на целевом разделе.
Необходимо учитывать возможность неожиданных результатов, когда все эти
триггеры влияют на перемещаемую строку. Что касается триггеров AFTER ROW
,
триггеры AFTER
DELETE
и AFTER
INSERT
применяются;
но триггеры AFTER
UPDATE
не применяются, потому что UPDATE
был преобразован в
DELETE
и INSERT
. Что касается
триггеров на уровне оператора, ни один из триггеров DELETE
или INSERT
не запускается, даже если происходит перемещение строк;
будут запущены только триггеры UPDATE
,
определенные на целевой таблице, используемой в операторе UPDATE
.
Вместо этого не определены отдельные триггеры для MERGE
. Вместо этого срабатывают триггеры на уровне оператора или строки для UPDATE
, DELETE
и INSERT
в зависимости от (для триггеров на уровне оператора) указанных действий в запросе MERGE
и (для триггеров на уровне строки) выполненных действий.
Во время выполнения команды MERGE
запускаются триггеры уровня оператора BEFORE
и AFTER
для событий, указанных в действиях команды MERGE
, независимо от того, выполняется ли действие в конечном итоге или нет. Это аналогично оператору UPDATE
, который не обновляет ни одной строки, но все равно запускает триггеры уровня оператора. Триггеры уровня строки запускаются только при фактическом обновлении, вставке или удалении строки. Поэтому вполне допустимо, что триггеры уровня оператора запускаются для определенных типов действий, но триггеры уровня строки не запускаются для того же типа действий.
Функции триггеров, вызываемые триггерами на уровне оператора, всегда должны возвращать NULL
. Функции триггеров, вызываемые триггерами на уровне строки, могут вернуть строку таблицы (значение типа HeapTuple
) вызывающему исполнителю, если они выберут. Триггер на уровне строки, сработавший перед операцией, имеет следующие варианты выбора:
Он может вернуть
NULL
, чтобы пропустить операцию для текущей строки. Это указывает исполнителю не выполнять операцию на уровне строки, которая вызвала триггер (вставку, изменение или удаление определенной строки таблицы).Для триггеров на уровне строк, выполняющих команды
INSERT
иUPDATE
, возвращаемая строка становится строкой, которая будет вставлена или заменит обновляемую строку. Это позволяет триггерной функции изменять вставляемую или обновляемую строку.
Триггер BEFORE
на уровне строки, который не предполагает вызова
ни одного из этих поведений, должен быть осторожным и возвращать в качестве результата ту же самую
строку, которая была передана (то есть строку NEW
для триггеров INSERT
и UPDATE
,
строку OLD
для триггеров
DELETE
).
Триггер на уровне строки INSTEAD OF
должен либо возвращать
NULL
, чтобы указать, что он не изменял данные из
базовых таблиц представления, либо возвращать строку представления,
которая была передана (строку NEW
для операций INSERT
и UPDATE
,
или строку OLD
для операций DELETE
). Ненулевое значение
используется для сигнализации о том, что триггер выполнил необходимые
изменения данных в представлении. Это приведет к увеличению количества
затронутых командой строк. Только для операций INSERT
и UPDATE
триггер
может изменять строку NEW
перед ее возвратом. Это
изменит данные, возвращаемые при использовании
INSERT RETURNING
или UPDATE RETURNING
,
и полезно, когда представление не будет показывать точно те же данные,
которые были предоставлены.
Возвращаемое значение игнорируется для триггеров на уровне строк, срабатывающих после операции, поэтому они могут возвращать NULL
.
Некоторые соображения применяются к генерируемым столбцам.
Хранимые генерируемые столбцы вычисляются после триггеров BEFORE
и перед триггерами AFTER
. Поэтому генерируемое значение можно проверить в триггерах AFTER
. В триггерах BEFORE
старое генерируемое значение содержится в строке OLD
, как и ожидается, но строка NEW
еще не содержит нового генерируемого значения и к ней не следует обращаться. В интерфейсе на языке C содержимое столбца в этот момент не определено; язык программирования более высокого уровня должен предотвращать доступ к хранимому генерируемому столбцу в строке NEW
в триггере BEFORE
. Изменения значения генерируемого столбца в триггере BEFORE
игнорируются и будут перезаписаны.
Если для одного и того же события на одной и той же связи определено более одного триггера, триггеры будут запускаться в алфавитном порядке по имени триггера. В случае триггеров BEFORE
и INSTEAD OF
измененная, возможно, строка, возвращаемая каждым триггером, становится входными данными для следующего триггера. Если какой-либо триггер BEFORE
или INSTEAD OF
возвращает NULL
, операция отменяется для этой строки, и последующие триггеры не запускаются (для этой строки).
Определение триггера также может указывать логическое условие WHEN
, которое будет проверяться для определения, должен ли триггер срабатывать. В триггерах на уровне строк условие WHEN
может проверять старые и/или новые значения столбцов строки. (Триггеры на уровне операторов также могут иметь условия WHEN
, хотя эта функция для них не так полезна). В триггере BEFORE
условие WHEN
вычисляется непосредственно перед выполнением или планируемым выполнением функции, поэтому использование WHEN
не отличается от проверки того же условия в начале триггерной функции. Однако в триггере AFTER
условие WHEN
вычисляется сразу после обновления строки и определяет, должно ли событие быть поставлено в очередь для срабатывания триггера в конце оператора. Поэтому, когда условие WHEN
триггера AFTER
не возвращает истинное значение, необходимо не ставить событие в очередь и не повторно извлекать строку в конце оператора. Это может привести к значительному увеличению скорости выполнения операторов, модифицирующих много строк, если триггер должен срабатывать только для нескольких строк. Триггеры INSTEAD OF
не поддерживают условия WHEN
.
Обычно, триггеры на уровне строки BEFORE
используются для проверки или изменения данных, которые будут вставлены или обновлены. Например, триггер BEFORE
может использоваться для вставки текущего времени в столбец timestamp
или для проверки согласованности двух элементов строки. Триггеры на уровне строки AFTER
наиболее разумно использовать для передачи обновлений в другие таблицы или для проверки согласованности с другими таблицами. Причина такого разделения труда заключается в том, что триггер AFTER
может быть уверен, что видит окончательное значение строки, в то время как триггер BEFORE
не может быть уверен; после него могут сработать другие триггеры BEFORE
. Если у вас нет конкретной причины сделать триггер BEFORE
или AFTER
, то вариант BEFORE
более эффективен, так как информация о операции не должна быть сохранена до конца оператора.
Если триггерная функция выполняет SQL-команды, то эти команды могут снова запускать триггеры. Это известно как каскадные триггеры. Нет прямого ограничения на количество уровней каскада. Возможно, каскады могут вызывать рекурсивное выполнение одного и того же триггера; например, триггер INSERT
может выполнить команду, которая вставляет дополнительную строку в ту же таблицу, вызывая срабатывание триггера INSERT
снова. Ответственность за избегание бесконечной рекурсии в таких сценариях лежит на разработчике, пишущем триггер.
При определении триггера можно указать аргументы для него. Цель включения аргументов в определение триггера заключается в том, чтобы позволить различным триггерам с похожими требованиями вызывать одну и ту же функцию. Например, может быть обобщенная триггерная функция, которая принимает в качестве аргументов два имена столбцов и помещает текущего пользователя в один столбец и текущую метку времени в другой. Правильно написанная такая триггерная функция будет независима от конкретной таблицы, на которой она срабатывает. Таким образом, одну и ту же функцию можно использовать для событий INSERT
на любой таблице с подходящими столбцами, чтобы автоматически отслеживать создание записей в таблице транзакций, например. Она также может использоваться для отслеживания событий последнего обновления, если определена как триггер UPDATE
.
Каждый язык программирования, поддерживающий триггеры, имеет свой собственный метод для предоставления входных данных триггера триггерной функции. Эти входные данные включают тип события триггера (например, INSERT
или UPDATE
), а также любые аргументы, указанные в CREATE TRIGGER
. Для триггера на уровне строки входные данные также включают строку NEW
для триггеров INSERT
и UPDATE
, а также строку OLD
для триггеров UPDATE
и DELETE
.
По умолчанию, триггеры на уровне оператора не имеют возможности анализировать отдельные измененные строки. Однако, триггер AFTER STATEMENT
может запросить создание таблиц перехода, чтобы сделать наборы затронутых строк доступными для триггера. Триггеры AFTER ROW
также могут запросить таблицы перехода, чтобы видеть общие изменения в таблице, а также изменения в отдельной строке, для которой они в данный момент запускаются. Метод анализа таблиц перехода снова зависит от используемого языка программирования, но типичным подходом является использование таблиц перехода как временных таблиц только для чтения, к которым можно обращаться с помощью SQL-команд, выполняемых внутри триггерной функции.