sql >> Base de Datos >  >> RDS >> PostgreSQL

Ejecute el desencadenador diferido solo una vez por fila en PostgreSQL

Este es un problema complicado. Pero se puede hacer con disparadores por columna y ejecución de disparadores condicionales introducidos en PostgreSQL 9.0 .

Necesita una bandera "actualizada" por fila para esta solución. Usa un boolean columna en la misma tabla por simplicidad. Pero podría estar en otra tabla o incluso en una tabla temporal por transacción.

La carga útil costosa se ejecuta una vez por fila donde se actualiza el contador (una o varias veces).

Esto también debería funcionar bueno, porque...

  • ... evita múltiples llamadas de disparadores en la raíz (escala bien)
  • ... no cambia las filas adicionales (minimiza la sobrecarga de la tabla)
  • ... no necesita un costoso manejo de excepciones.

Considere lo siguiente

Demostración

Probado en PostgreSQL 9.1 con un esquema separado x como entorno de prueba.

Tablas y filas ficticias

-- DROP SCHEMA x;
CREATE SCHEMA x;

CREATE TABLE x.tbl (
 id int
,counter int
,trig_exec_count integer  -- for monitoring payload execution.
,updated bool);

Inserte dos filas para demostrar que funciona con varias filas:

INSERT INTO x.tbl VALUES
 (1, 0, 0, NULL)
,(2, 0, 0, NULL);

Funciones de activación y activadores

1.) Ejecutar carga útil costosa

CREATE OR REPLACE FUNCTION x.trg_upaft_counter_change_1()
    RETURNS trigger AS
$BODY$
BEGIN

 -- PERFORM some_expensive_procedure(NEW.id);
 -- Update trig_exec_count to count execution of expensive payload.
 -- Could be in another table, for simplicity, I use the same:

UPDATE x.tbl t
SET    trig_exec_count = trig_exec_count + 1
WHERE  t.id = NEW.id;

RETURN NULL;  -- RETURN value of AFTER trigger is ignored anyway

END;
$BODY$ LANGUAGE plpgsql;

2.) Marcar fila como actualizada.

CREATE OR REPLACE FUNCTION x.trg_upaft_counter_change_2()
    RETURNS trigger AS
$BODY$
BEGIN

UPDATE x.tbl
SET    updated = TRUE
WHERE  id = NEW.id;
RETURN NULL;

END;
$BODY$ LANGUAGE plpgsql;

3.) Restablecer el indicador "actualizado".

CREATE OR REPLACE FUNCTION x.trg_upaft_counter_change_3()
    RETURNS trigger AS
$BODY$
BEGIN

UPDATE x.tbl
SET    updated = NULL
WHERE  id = NEW.id;
RETURN NULL;

END;
$BODY$ LANGUAGE plpgsql;

¡Los nombres de los disparadores son relevantes! Llamados para el mismo evento se ejecutan en orden alfabético.

1.) Carga útil, solo si aún no está "actualizada":

CREATE CONSTRAINT TRIGGER upaft_counter_change_1
    AFTER UPDATE OF counter ON x.tbl
    DEFERRABLE INITIALLY DEFERRED
    FOR EACH ROW
    WHEN (NEW.updated IS NULL)
    EXECUTE PROCEDURE x.trg_upaft_counter_change_1();

2.) Marque la fila como actualizada, solo si aún no está "actualizada":

CREATE TRIGGER upaft_counter_change_2   -- not deferred!
    AFTER UPDATE OF counter ON x.tbl
    FOR EACH ROW
    WHEN (NEW.updated IS NULL)
    EXECUTE PROCEDURE x.trg_upaft_counter_change_2();

3.) Restablecer bandera. Sin bucle sin fin debido a la condición de activación.

CREATE CONSTRAINT TRIGGER upaft_counter_change_3
    AFTER UPDATE OF updated ON x.tbl
    DEFERRABLE INITIALLY DEFERRED
    FOR EACH ROW
    WHEN (NEW.updated)                 --
    EXECUTE PROCEDURE x.trg_upaft_counter_change_3();

Prueba

Ejecute UPDATE &SELECT por separado para ver el efecto diferido. Si se ejecutan juntos (en una transacción), SELECT mostrará el nuevo tbl.counter pero el viejo tbl2.trig_exec_count .

UPDATE x.tbl SET counter = counter + 1;

SELECT * FROM x.tbl;

Ahora, actualice el contador varias veces (en una transacción). La carga útil solo se ejecutará una vez. ¡Voilá!

UPDATE x.tbl SET counter = counter + 1;
UPDATE x.tbl SET counter = counter + 1;
UPDATE x.tbl SET counter = counter + 1;
UPDATE x.tbl SET counter = counter + 1;
UPDATE x.tbl SET counter = counter + 1;

SELECT * FROM x.tbl;