2016-08-13 1 views
1

I possède une table a avec trois déclencheurs qui insèrent, mettre à jour ou supprimer des lignes correspondantes dans b chaque fois une rangée dans a est inséré, mis à jour ou supprimés. Tous les 3 déclencheurs utilisent la même fonction de déclenchement p.jointures autour if pour la fonction de déclenchement PostgreSQL

CREATE OR REPLACE FUNCTION p() 
RETURNS TRIGGER 
AS $$ 
BEGIN 
    IF (TG_OP = 'INSERT') THEN 
    -- INSERT INTO b ... 
    RETURN NEW; 
    ELSIF (TG_OP = 'UPDATE') THEN 
    -- UPDATE b ... 
    RETURN NEW; 
    ELSIF (TG_OP = 'DELETE') THEN 
    -- DELETE FROM b ... 
    RETURN NEW; 
    ELSE 
    RETURN NULL; 
    END IF; 
END; 
$$ LANGUAGE PLPGSQL; 

CREATE TRIGGER i AFTER INSERT ON a FOR EACH ROW EXECUTE PROCEDURE p(); 
CREATE TRIGGER u AFTER UPDATE ON a FOR EACH ROW EXECUTE PROCEDURE p(); 
CREATE TRIGGER d AFTER DELETE ON a FOR EACH ROW EXECUTE PROCEDURE p(); 

a a aussi une clé étrangère a1 dans c (avec c1 clé primaire), et je voudrais modifier p de telle sorte qu'il entre dans les IF/ELSIF branches en fonction également sur une colonne c2 dans c : si cette colonne jointe a été modifiée, entrez les branches INSERT et UPDATE; s'il reste le même, entrez la branche UPDATE. En effet, quelque chose comme ceci:

IF (TG_OP = 'INSERT') OR ((TG_OP = 'UPDATE') AND (oldC.c2 <> newC.c2)) THEN 
    -- ... 
    ELSIF (TG_OP = 'UPDATE') OR (oldC.c2 = newC.c2) THEN 
    -- ... 
    ELSIF (TG_OP = 'DELETE') OR ((TG_OP = 'UPDATE') AND (oldC.c2 <> newC.c2)) THEN 
    -- ... 
    ELSE 
    -- ... 
    END IF; 

oldC et newC résulterait de jointures similaires à ceux-ci (avec la syntaxe environ.):

SELECT oldC.* FROM a, c AS oldC WHERE OLD.a1 = c.c1; 
SELECT newC.* FROM a, c AS newC WHERE NEW.a1 = c.c1; 

donc ce qui est nécessaire en vigueur sont deux jointures en dehors de la IF, ce qui lui permettrait de se référer à oldC et newC (ou quelque chose d'analogue). Est-ce possible et quelle serait la version modifiée de p (avec une syntaxe PostgreSQL correcte)?

Répondre

3

Tout d'abord, il n'y a pas NEW dans le cas d'un DELETE, donc RETURN NEW; n'a pas de sens et déclencherait une exception. Peu importe vous retournez pour AFTER déclencheurs de toute façon. Autant être RETURN NULL;

Ensuite, vous ne pouvez pas référence à OLD en cas d'INSERT, que ce soit. Vous devez vérifier le type d'opération avant en vous référant à OLD ou NEW.

Et vous seulement besoin d'un unique déclencheur la façon dont vous avez en ce moment:

CREATE TRIGGER a_i_u_d -- *one* trigger 
AFTER INSERT OR UPDATE OR DELETE ON a 
FOR EACH ROW EXECUTE PROCEDURE p(); 

Cependant, je suggère fonctions de déclenchement séparées pour INSERT, UPDATE et DELETE pour éviter les complications. Vous avez ensuite besoin de trois déclencheurs distincts, chacun appelant sa fonction de déclenchement respective.

Le cas que vous souhaitez ajouter peut seulement affecter UPDATE. Rien ne peut "changer" comme vous le décrivez avec INSERT ou DELETE.

Et à proprement parler, ce que vous demandez est impossible même pour un UPDATE déclencheur:

selon une colonne c2 dans c: si cette colonne a rejoint changé ...

Une fonction de déclenchement d'une table a ne voit qu'un seul instantanéde la table c. Il n'y a aucun moyen de détecter un "changement" dans cette table. Si vous avez vraiment voulu écrire:

fonction de la colonne a.a1: si cela a changé de sorte que la valeur référencée c.c2 est différent maintenant ...

.. alors il est un moyen :

Depuis un déclencheur BEFORE est moins enclin à des boucles sans fin et d'autres complications, je montre un déclencheur BEFORE UPDATE. (Le passage à AFTER est trivial.):

CREATE OR REPLACE FUNCTION p_upbef() 
    RETURNS trigger AS 
$func$ 
BEGIN 
    IF NEW.a1 <> OLD.a1 THEN -- assuming a1 is defined NOT NULL 
     IF (SELECT count(DISTINCT c.c2) > 1 -- covers possible NULL in c2 as well 
      FROM c 
      WHERE c.c1 IN (NEW.a1, OLD.a1)) THEN 
      -- do something 
     END IF; 
    END IF; 

    RETURN NEW; 
END 
$func$ LANGUAGE plpgsql; 

Si a1 peut être NULL et vous devez suivre les changements de/à NULL ainsi, vous devez faire plus ...

Trigger:

CREATE TRIGGER upbef 
BEFORE UPDATE ON a 
FOR EACH ROW EXECUTE PROCEDURE p_upbef(); 

Puisque tout dépend d'un changement de a.a1 maintenant (et vous n'avez pas d'autres choses dans la gâchette), vous pouvez déplacer le IF externe à la gâchette lui-même (moins cher):

CREATE OR REPLACE FUNCTION p_upbef() 
    RETURNS trigger AS 
$func$ 
BEGIN 
    IF (SELECT count(DISTINCT c.c2) > 1 -- covers NULL as well 
     FROM c 
     WHERE c.c1 IN (NEW.a1, OLD.a1)) THEN -- assuming a1 is NOT NULL! 
     -- do something 
    END IF; 

    RETURN NEW; 
END 
$func$ LANGUAGE plpgsql; 

Trigger:

CREATE TRIGGER upbef 
BEFORE UPDATE OF a1 ON a 
FOR EACH ROW EXECUTE PROCEDURE p_upbef();

Ce n'est pas exactement même, étant donné qu'un UPDATEimpliquant la colonne a1 pourrait effectivement laisser la valeur inchangée, mais il est assez bon de toute façon pour notre objectif: pour exécuter la vérification coûteuse sur c.c2 dans les cas pertinents.

connexes:

+0

+1 Est-il exact de supposer que je dois placer un déclencheur/utre sur 'C' pour la première interprétation (» en fonction d'une colonne c2 dans c: si cette colonne jointe a changé ")? – Drux

+1

@Drux: Oui, pour surveiller les changements dans 'c.c2', vous avez besoin d'un trigger sur' c'. Attention à ne pas créer de boucles infinies avec trop de déclencheurs. Parfois, les requêtes combinées utilisant des CTE modificateurs de données constituent une bonne alternative. [Exemple.] (Http://stackoverflow.com/questions/20561254/insert-data-in-3-tables-at-a-time-using-postgres/20561627#20561627) –