Un disparador (trigger) es una especificación de que la base de datos debe ejecutar automáticamente una función particular cada vez que se realice un cierto tipo de operación. Los disparadores se pueden asociar a tablas (particionadas o no), vistas y tablas foráneas.
En tablas y tablas foráneas, los disparadores se pueden definir para que se ejecuten
antes o después de cualquier operación INSERT, UPDATE
o DELETE, ya sea una vez por fila modificada, o una vez
por sentencia SQL.
Los disparadores de UPDATE se pueden configurar además para que se activen
sólo si se mencionan ciertas columnas en la cláusula SET de la sentencia
UPDATE. Los disparadores también se pueden activar para sentencias
TRUNCATE. Si ocurre un evento de disparo, la función del disparador se
llama en el momento apropiado para manejar el evento.
En las vistas, los disparadores se pueden definir para ejecutarse en lugar de
las operaciones INSERT, UPDATE o
DELETE.
Tales disparadores INSTEAD OF se activan una vez por cada fila
que deba modificarse en la vista. Es responsabilidad de la función del disparador realizar
las modificaciones necesarias en la(s) tabla(s) base subyacente(s) de la vista y, cuando
sea apropiado, devolver la fila modificada tal como aparecerá en la vista.
Los disparadores en las vistas también se pueden definir para ejecutarse una vez por sentencia
SQL, antes o después de las operaciones INSERT,
UPDATE o DELETE. Sin embargo, estos disparadores sólo se
activan si también hay un disparador INSTEAD OF en la vista. De lo contrario,
cualquier sentencia dirigida a la vista debe reescribirse en una sentencia que afecte a su(s)
tabla(s) base subyacente(s), y entonces los disparadores que se activarán serán los asociados a
la(s) tabla(s) base.
La función del disparador debe definirse antes de que se pueda crear el disparador en sí.
La función del disparador debe declararse como una función que no toma argumentos
y devuelve el tipo trigger. (La función del disparador recibe su
entrada a través de una estructura TriggerData que se pasa de
manera especial, no en forma de argumentos de función ordinarios).
Una vez que se ha creado una función de disparo adecuada, el disparador se establece con CREATE TRIGGER. Se puede usar la misma función de disparo para múltiples disparadores.
PostgreSQL ofrece tanto disparadores por fila (per-row)
como disparadores por sentencia (per-statement). Con un disparador por fila,
la función del disparador se invoca una vez por cada fila afectada por la sentencia que activó
el disparador. En cambio, un disparador por sentencia se invoca una sola vez cuando se ejecuta la
sentencia correspondiente, independientemente del número de filas afectadas por esa sentencia. En
particular, una sentencia que afecte a cero filas seguirá dando lugar a la ejecución de cualquier
disparador por sentencia aplicable. Estos dos tipos de disparadores a veces se llaman disparadores
a nivel de fila (row-level) y disparadores a nivel de sentencia (statement-level),
respectivamente. Los disparadores en TRUNCATE sólo se pueden definir a nivel de
sentencia, no por fila.
Los disparadores también se clasifican según si se activan antes (before),
después (after) o en lugar de (instead of) la operación.
Se denominan disparadores BEFORE, disparadores AFTER y
disparadores INSTEAD OF respectivamente.
Los disparadores BEFORE a nivel de sentencia naturalmente se activan antes de que la
sentencia comience a hacer algo, mientras que los disparadores AFTER a nivel de sentencia
se activan al final de la sentencia. Estos tipos de disparadores se pueden definir en tablas, vistas o
tablas foráneas. Los disparadores BEFORE a nivel de fila se activan inmediatamente antes
de operar sobre una fila en particular, mientras que los disparadores AFTER a nivel de fila
se activan al final de la sentencia (pero antes de cualquier disparador AFTER a nivel de
sentencia). Estos tipos de disparadores sólo se pueden definir en tablas y tablas foráneas, no en vistas.
Los disparadores INSTEAD OF sólo se pueden definir en vistas, y sólo a nivel de fila;
se activan inmediatamente cuando se identifica que cada fila de la vista necesita ser operada.
La ejecución de un disparador AFTER se puede posponer hasta el final de la transacción,
en lugar de al final de la sentencia, si se definió como un disparador de restricción
(constraint trigger). En todos los casos, un disparador se ejecuta como parte de la misma transacción
que la sentencia que lo activó, por lo que si la sentencia o el disparador causan un error, los efectos
de ambos se revertirán. Además, el disparador siempre se ejecutará con el rol que puso en cola el
evento de disparo, a menos que la función del disparador esté marcada como SECURITY DEFINER,
en cuyo caso se ejecutará como el propietario de la función.
Si un INSERT contiene una cláusula ON CONFLICT DO UPDATE,
es posible que se ejecuten disparadores BEFORE INSERT a nivel de
fila y luego disparadores BEFORE UPDATE en las filas afectadas.
Estas interacciones pueden ser complejas si los disparadores no son idempotentes porque los cambios
realizados por los disparadores BEFORE INSERT serán vistos por
los disparadores BEFORE UPDATE, incluidos los cambios en las
columnas EXCLUDED.
Ten en cuenta que los disparadores UPDATE a nivel de sentencia se ejecutan
cuando se especifica ON CONFLICT DO UPDATE, independientemente de si el
UPDATE afectó o no a alguna fila (e independientemente de si se tomó alguna
vez la ruta alternativa de UPDATE). Un INSERT con una
cláusula ON CONFLICT DO UPDATE ejecutará primero los disparadores BEFORE
INSERT a nivel de sentencia, luego los disparadores BEFORE
UPDATE a nivel de sentencia, seguidos por los disparadores AFTER
UPDATE a nivel de sentencia y finalmente los disparadores AFTER
INSERT a nivel de sentencia.
Una sentencia dirigida a una tabla padre en una jerarquía de herencia o particionamiento no hace que se activen los disparadores a nivel de sentencia de las tablas hijas afectadas; sólo se activan los disparadores a nivel de sentencia de la tabla padre. Sin embargo, los disparadores a nivel de fila de cualquier tabla hija afectada sí se activarán.
Si un UPDATE en una tabla particionada hace que una fila se mueva a otra
partición, se realizará como un DELETE de la partición original seguido de un
INSERT en la nueva partición. En este caso, todos los disparadores BEFORE
UPDATE a nivel de fila y todos los disparadores BEFORE
DELETE a nivel de fila se activan en la partición original. Luego, todos los
disparadores BEFORE INSERT a nivel de fila se activan en
la partición de destino. Se debe considerar la posibilidad de resultados sorprendentes cuando todos
estos disparadores afectan a la fila que se está moviendo. En lo que respecta a los disparadores
AFTER ROW, se aplican los disparadores AFTER DELETE
y AFTER INSERT; pero los disparadores AFTER
UPDATE no se aplican porque el UPDATE se ha convertido en un
DELETE y un INSERT. En lo que respecta a los disparadores
a nivel de sentencia, ninguno de los disparadores de DELETE o INSERT
se activa, incluso si ocurre movimiento de filas; sólo se activarán los disparadores de UPDATE
definidos en la tabla de destino utilizada en la sentencia UPDATE.
No se definen disparadores separados para MERGE. En su lugar, se activan
disparadores de UPDATE, DELETE e INSERT
a nivel de sentencia o de fila dependiendo de (para disparadores a nivel de sentencia) qué acciones
se especifican en la consulta MERGE y (para disparadores a nivel de fila) qué
acciones se realizan.
Durante la ejecución de un comando MERGE, se activan disparadores BEFORE
y AFTER a nivel de sentencia para los eventos especificados en las acciones del
comando MERGE, independientemente de si la acción se realiza o no en última
instancia. Esto es lo mismo que una sentencia UPDATE que no actualiza ninguna
fila, y aún así se activan los disparadores a nivel de sentencia. Los disparadores a nivel de fila
se activan sólo cuando una fila es realmente actualizada, insertada o eliminada. Por lo tanto, es
perfectamente legal que mientras se activan disparadores a nivel de sentencia para ciertos tipos de
acciones, no se activen disparadores a nivel de fila para el mismo tipo de acción.
Las funciones de disparo invocadas por disparadores por sentencia siempre deben devolver NULL.
Las funciones de disparo invocadas por disparadores por fila pueden devolver una fila de la tabla (un
valor de tipo HeapTuple) al ejecutor invocador, si así lo deciden. Un disparador
a nivel de fila activado antes de una operación tiene las siguientes opciones:
Puede devolver NULL para omitir la operación para la fila actual. Esto le indica al
ejecutor que no realice la operación a nivel de fila que invocó al disparador (la inserción,
modificación o eliminación de una fila de tabla en particular).
Sólo para disparadores INSERT y UPDATE a nivel de fila,
la fila devuelta se convierte en la fila que se insertará o que reemplazará a la fila que se está
actualizando. Esto permite que la función del disparador modifique la fila que se está insertando
o actualizando.
Un disparador BEFORE a nivel de fila que no tenga la intención de causar ninguno de
estos comportamientos debe tener cuidado de devolver como resultado la misma fila que se le pasó
(es decir, la fila NEW para los disparadores INSERT y
UPDATE, la fila OLD para los disparadores DELETE).
Un disparador INSTEAD OF a nivel de fila debe devolver NULL para
indicar que no modificó ningún dato de las tablas base subyacentes de la vista, o debe devolver la
fila de la vista que se le pasó (la fila NEW para las operaciones INSERT
y UPDATE, o la fila OLD para las operaciones DELETE).
Se utiliza un valor de retorno que no sea nulo para indicar que el disparador realizó las modificaciones
de datos necesarias en la vista. Esto hará que se incremente el conteo del número de filas afectadas por
el comando. Sólo para operaciones INSERT y UPDATE, el disparador puede
modificar la fila NEW antes de devolverla. Esto cambiará los datos devueltos por
INSERT RETURNING o UPDATE RETURNING, y es útil cuando la vista no
mostrará exactamente los mismos datos que se proporcionaron.
El valor de retorno se ignora para los disparadores a nivel de fila activados después de una operación,
por lo que pueden devolver NULL.
Se aplican algunas consideraciones para las columnas generadas. Las columnas generadas almacenadas se calculan después de los
disparadores BEFORE y antes de los disparadores AFTER. Por lo
tanto, el valor generado se puede inspeccionar en los disparadores AFTER. En los
disparadores BEFORE, la fila OLD contiene el valor generado antiguo,
como era de esperar, pero la fila NEW aún no contiene el nuevo valor generado y no
debe accederse. En la interfaz del lenguaje C, el contenido de la columna no está definido en este punto;
un lenguaje de programación de nivel superior debería impedir el acceso a una columna generada almacenada
en la fila NEW en un disparador BEFORE. Los cambios en el valor de una
columna generada en un disparador BEFORE se ignoran y se sobrescribirán. Las columnas
generadas virtuales nunca se calculan cuando se activan los disparadores. En la interfaz del lenguaje C,
su contenido no está definido en una función de disparo. Los lenguajes de programación de nivel superior
deberían impedir el acceso a las columnas generadas virtuales en los disparadores.
Si se define más de un disparador para el mismo evento en la misma relación, los disparadores se activarán
en orden alfabético por el nombre del disparador. En el caso de los disparadores BEFORE y
INSTEAD OF, la fila posiblemente modificada devuelta por cada disparador se convierte en
la entrada para el siguiente disparador. Si algún disparador BEFORE o
INSTEAD OF devuelve NULL, la operación se abandona para esa fila
y no se activan los disparadores subsiguientes (para esa fila).
La definición de un disparador también puede especificar una condición booleana WHEN,
que se evaluará para ver si se debe activar el disparador. En los disparadores a nivel de fila, la
condición WHEN puede examinar los valores antiguos y/o nuevos de las columnas de la
fila. (Los disparadores a nivel de sentencia también pueden tener condiciones WHEN,
aunque la característica no es tan útil para ellos). En un disparador BEFORE, la
condición WHEN se evalúa justo antes de que la función se ejecute o se fuera a ejecutar,
por lo que usar WHEN no es materialmente diferente de probar la misma condición al
comienzo de la función del disparador. Sin embargo, en un disparador AFTER, la condición
WHEN se evalúa justo después de que ocurra la actualización de la fila, y determina si un
evento se pone en cola para activar el disparador al final de la sentencia. Por lo tanto, cuando la condición
WHEN de un disparador AFTER no devuelve true, no es necesario poner
en cola un evento ni volver a recuperar la fila al final de la sentencia. Esto puede resultar en
aceleraciones significativas en sentencias que modifican muchas filas, si el disparador sólo necesita
activarse para unas pocas de las filas. Los disparadores INSTEAD OF no admiten condiciones
WHEN.
Normalmente, los disparadores BEFORE a nivel de fila se utilizan para verificar o modificar
los datos que se insertarán o actualizarán. Por ejemplo, un disparador BEFORE podría
usarse para insertar la hora actual en una columna de tipo timestamp, o para verificar que
dos elementos de la fila sean consistentes. Los disparadores AFTER a nivel de fila se
utilizan de manera más lógica para propagar las actualizaciones a otras tablas, o para realizar comprobaciones
de consistencia con respecto a otras tablas. La razón de esta división del trabajo es que un disparador
AFTER puede estar seguro de que está viendo el valor final de la fila, mientras que un
disparador BEFORE no puede; podría haber otros disparadores BEFORE
activándose después de él. Si no tienes una razón específica para hacer que un disparador sea
BEFORE o AFTER, el caso BEFORE es más eficiente,
ya que la información sobre la operación no tiene que guardarse hasta el final de la sentencia.
Si una función de disparo ejecuta comandos SQL, estos comandos podrían activar disparadores nuevamente.
Esto se conoce como disparadores en cascada (cascading triggers). No existe una limitación directa sobre el
número de niveles de cascada. Es posible que las cascadas causen una invocación recursiva del mismo
disparador; por ejemplo, un disparador de INSERT podría ejecutar un comando que inserte
una fila adicional en la misma tabla, lo que haría que el disparador de INSERT se activara
nuevamente. Es responsabilidad del programador del disparador evitar la recursión infinita en tales escenarios.
Si una restricción de clave foránea especifica acciones referenciales (es decir, actualizaciones o
eliminaciones en cascada), esas acciones se realizan mediante comandos SQL ordinarios UPDATE
o DELETE en la tabla de referencia. En particular, cualquier disparador que exista en la
tabla de referencia se activará para esos cambios. Si dicho disparador modifica o bloquea el efecto de uno
de estos comandos, el resultado final podría ser romper la integridad referencial. Es responsabilidad del
programador del disparador evitar eso.
Cuando se define un disparador, se pueden especificar argumentos para él. El propósito de incluir
argumentos en la definición del disparador es permitir que diferentes disparadores con requisitos
similares llamen a la misma función. Como ejemplo, podría haber una función de disparo generalizada
que tome como argumentos dos nombres de columna y coloque al usuario actual en una y la marca de tiempo
actual en la otra. Escrita correctamente, esta función de disparo sería independiente de la tabla
específica en la que se activa. De modo que la misma función podría usarse para eventos INSERT
en cualquier tabla con columnas adecuadas, para rastrear automáticamente la creación de registros en una
tabla de transacciones, por ejemplo. También podría usarse para rastrear eventos de última actualización si
se define como un disparador de UPDATE.
Cada lenguaje de programación que admite disparadores tiene su propio método para hacer que los datos de
entrada del disparador estén disponibles para la función del disparador. Estos datos de entrada incluyen el
tipo de evento del disparador (por ejemplo, INSERT o UPDATE), así
como cualquier argumento que se haya enumerado en CREATE TRIGGER. Para un disparador a
nivel de fila, los datos de entrada también incluyen la fila NEW para los disparadores
INSERT y UPDATE, y/o la fila OLD para los
disparadores UPDATE y DELETE.
Por defecto, los disparadores a nivel de sentencia no tienen ninguna forma de examinar la(s) fila(s)
individual(es) modificada(s) por la sentencia. Pero un disparador AFTER STATEMENT
puede solicitar que se creen tablas de transición (transition tables) para que
los conjuntos de filas afectadas estén disponibles para el disparador. Los disparadores AFTER ROW
también pueden solicitar tablas de transición, de modo que puedan ver los cambios totales en la tabla,
así como el cambio en la fila individual para la que se están activando actualmente. El método para
examinar las tablas de transición depende nuevamente del lenguaje de programación que se esté utilizando,
pero el enfoque típico es hacer que las tablas de transición actúen como tablas temporales de sólo lectura
a las que se puede acceder mediante comandos SQL emitidos dentro de la función del disparador.