C'est un problème délicat. Mais cela peut être fait avec des déclencheurs par colonne et l'exécution de déclencheurs conditionnels introduits dans PostgreSQL 9.0 .
Vous avez besoin d'un indicateur "mis à jour" par ligne pour cette résolution. Utiliser un boolean
colonne dans le même tableau pour plus de simplicité. Mais cela pourrait être dans une autre table ou même une table temporaire par transaction.
La charge utile coûteuse est exécutée une fois par ligne où le compteur est mis à jour (une ou plusieurs fois).
Cela devrait également fonctionner eh bien, parce que ...
- ... cela évite plusieurs appels de déclencheurs à la racine (s'adapte bien)
- ... ne modifie pas les lignes supplémentaires (réduit l'encombrement du tableau)
- ... n'a pas besoin d'une gestion coûteuse des exceptions.
Considérez ce qui suit
Démo
Testé dans PostgreSQL 9.1 avec un schéma séparé x
comme environnement de test.
Tableaux et lignes factices
-- DROP SCHEMA x;
CREATE SCHEMA x;
CREATE TABLE x.tbl (
id int
,counter int
,trig_exec_count integer -- for monitoring payload execution.
,updated bool);
Insérez deux lignes pour démontrer que cela fonctionne avec plusieurs lignes :
INSERT INTO x.tbl VALUES
(1, 0, 0, NULL)
,(2, 0, 0, NULL);
Fonctions de déclenchement et déclencheurs
1.) Exécutez une charge utile coûteuse
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.) Signaler la ligne comme mise à jour.
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.) Réinitialisez le drapeau "mis à jour".
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;
Les noms des déclencheurs sont pertinents ! Appelés pour le même événement ils sont exécutés par ordre alphabétique.
1.) Payload, uniquement s'il n'est pas encore "mis à jour" :
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.) Marquer la ligne comme mise à jour, uniquement si elle n'est pas encore "mise à jour" :
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.) Drapeau de réinitialisation. Pas de boucle sans fin à cause de la condition de déclenchement.
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();
Tester
Exécutez UPDATE
&SELECT
séparément pour voir l'effet différé. S'il est exécuté ensemble (en une seule transaction), le SELECT affichera le nouveau tbl.counter
mais l'ancien tbl2.trig_exec_count
.
UPDATE x.tbl SET counter = counter + 1;
SELECT * FROM x.tbl;
Maintenant, mettez à jour le compteur plusieurs fois (en une seule transaction). La charge utile ne sera exécutée qu'une seule fois. 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;