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;