web-dev-qa-db-fra.com

INSERT avec le nom de la table dynamique dans la fonction de déclenchement

Je ne suis pas sûr de savoir comment réaliser quelque chose comme ceci:

CREATE OR REPLACE FUNCTION fnJobQueueBEFORE() RETURNS trigger AS $$
    DECLARE
        shadowname varchar := TG_TABLE_NAME || 'shadow';
    BEGIN
        INSERT INTO shadowname VALUES(OLD.*);
        RETURN OLD;
    END;
$$
LANGUAGE plpgsql;

C'est à dire. insertion de valeurs dans une table avec un nom généré dynamiquement.
L’exécution du code ci-dessus donne:

ERROR:  relation "shadowname" does not exist
LINE 1: INSERT INTO shadowname VALUES(OLD.*)

Il semble suggérer que les variables ne sont pas développées/autorisées en tant que noms de table. Je n'ai trouvé aucune référence à cela dans le manuel de Postgres.

J'ai déjà expérimenté avec EXECUTE comme ceci:

  EXECUTE 'INSERT INTO ' || quote_ident(shadowname) || ' VALUES ' || OLD.*;

Mais pas de chance:

ERROR:  syntax error at or near ","
LINE 1: INSERT INTO personenshadow VALUES (1,sven,,,)

Le type RECORD semble être perdu: OLD.* semble avoir été converti en une chaîne et obtenir une reparation, générant toutes sortes de problèmes de type (par exemple, les valeurs NULL).

Des idées?

27
sschober

PostgreSQL 9.1 ou version ultérieure

format() a un moyen intégré pour échapper aux identifiants. Plus simple qu'avant:

CREATE OR REPLACE FUNCTION foo_before()
  RETURNS trigger AS
$func$
BEGIN
   EXECUTE format('INSERT INTO %I.%I SELECT $1.*'
                , TG_TABLE_SCHEMA, TG_TABLE_NAME || 'shadow')
   USING OLD;

   RETURN OLD;
END
$func$  LANGUAGE plpgsql;

SQL Fiddle.
Fonctionne également avec une expression VALUES .

Points majeurs

  • Utilisez format () ou quote_ident() pour citer des identificateurs, le cas échéant, et vous défendre contre injection SQL .
    Ceci est nécessaire, même avec vos propres noms de table!
  • Schéma-qualifiez le nom de la table. En fonction du paramètre search_path actuel , un nom de table nu peut sinon être résolu en une autre table du même nom dans un schéma différent.
  • Utilisez EXECUTE pour les instructions DDL dynamiques.
  • Passez valeurs en toute sécurité avec la clause USING.
  • Consultez le manuel détaillé sur Exécution de commandes dynamiques dans plpgsql .
  • Notez queRETURN OLD; dans la fonction de déclenchement est requis pour un déclencheur BEFORE DELETE. Détails dans le manuel ici.

Vous obtenez le message error dans votre version presque réussie car OLD est non visible dans EXECUTE. Et si vous souhaitez concaténer les valeurs individuelles de la ligne décomposée comme vous l'avez essayé, vous devez préparer la représentation textuelle de chaque colonne avec quote_literal() pour garantir une syntaxe valide. Vous devez également connaître nom de colonne à l'avance pour pouvoir les manipuler ou interroger les catalogues système - ce qui va à l'encontre de votre idée de disposer d'une fonction de déclenchement simple et dynamique ...

Ma solution évite toutes ces complications. Aussi simplifié un peu.

PostgreSQL 9.0 ou version antérieure

format() n'est pas encore disponible, donc:

CREATE OR REPLACE FUNCTION foo_before()
  RETURNS trigger AS
$func$
BEGIN
    EXECUTE 'INSERT INTO ' || quote_ident(TG_TABLE_SCHEMA)
                    || '.' || quote_ident(TG_TABLE_NAME || 'shadow')
                    || ' SELECT $1.*'
    USING OLD;

    RETURN OLD;
END
$func$  LANGUAGE plpgsql;

En relation:

49
Erwin Brandstetter

Je suis tombé sur cela parce que je cherchais un déclencheur dynamique INSTEAD OF DELETE. En guise de remerciement pour la question et les réponses, je posterai ma solution pour Postgres 9.3.

CREATE OR REPLACE FUNCTION set_deleted_instead_of_delete()
RETURNS TRIGGER AS $$
BEGIN
    EXECUTE format('UPDATE %I set deleted = now() WHERE id = $1.id', TG_TABLE_NAME)
    USING OLD;
    RETURN NULL;
END;
$$ language plpgsql;
1
robkorv