PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Exécuter le déclencheur différé une seule fois par ligne dans PostgreSQL

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;