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

Mettre à jour plusieurs colonnes dans une fonction de déclenchement dans plpgsql

Bien que la réponse de @ Gary soit techniquement correcte, il omet de mentionner que PostgreSQL fait prendre en charge ce formulaire :

UPDATE tbl
SET (col1, col2, ...) = (expression1, expression2, ..)

Lire le manuel sur UPDATE encore une fois.

Il est toujours difficile de faire son travail avec SQL dynamique. Puisque vous n'avez pas spécifié, je suppose un cas simple où les vues se composent des mêmes colonnes que leurs tables sous-jacentes.

CREATE VIEW tbl_view AS SELECT * FROM tbl;

Problèmes

  • L'enregistrement spécial NEW n'est pas visible dans EXECUTE . Je passe NEW en tant que paramètre unique avec le USING clause de EXECUTE .

  • Comme indiqué, UPDATE avec une forme de liste nécessite des valeurs individuelles . J'utilise une sous-sélection pour diviser l'enregistrement en colonnes individuelles :

    UPDATE ...
    FROM  (SELECT ($1).*) x
    

    (Parenthèse autour de $1 ne sont pas facultatifs.) Cela me permet d'utiliser simplement deux listes de colonnes construites avec string_agg() depuis la table catalogue :une avec et une sans qualification de table.

  • Il n'est pas possible d'affecter une valeur de ligne dans son ensemble à des colonnes individuelles. Le manuel :

    Selon la norme, la valeur source d'une sous-liste entre parenthèses de noms de colonnes cibles peut être n'importe quelle expression de valeur de ligne produisant le nombre correct de colonnes. PostgreSQL permet uniquement à la valeur source d'être un constructeur de ligne ou un sous-SELECT .

  • INSERT est mis en œuvre plus simplement. En supposant que la structure de la vue et de la table sont identiques, j'omets la liste des définitions de colonne. (Peut être amélioré, voir ci-dessous.)

Solution

J'ai apporté un certain nombre de mises à jour à votre approche pour la faire briller.

Fonction de déclenchement pour UPDATE :

CREATE OR REPLACE FUNCTION f_trg_up()
  RETURNS TRIGGER AS
$func$
DECLARE
   tbl  text := quote_ident(TG_TABLE_SCHEMA) || '.'
             || quote_ident(substring(TG_TABLE_NAME from '(.+)_view$'));
   cols text;
   vals text;
BEGIN
   SELECT INTO cols, vals
          string_agg(quote_ident(attname), ', ')
         ,string_agg('x.' || quote_ident(attname), ', ')
   FROM   pg_attribute
   WHERE  attrelid = tbl::regclass
   AND    NOT attisdropped   -- no dropped (dead) columns
   AND    attnum > 0;        -- no system columns

   EXECUTE format('
   UPDATE %s t
   SET   (%s) = (%s)
   FROM  (SELECT ($1).*) x
   WHERE  t.id = ($2).id'
   , tbl, cols, vals) -- assuming unique "id" in every table
   USING NEW, OLD;

   RETURN NEW;
END
$func$ LANGUAGE plpgsql;

Fonction de déclenchement pour INSERT :

CREATE OR REPLACE FUNCTION f_trg_ins()
  RETURNS TRIGGER AS
$func$
DECLARE
    tbl text := quote_ident(TG_TABLE_SCHEMA) || '.'
             || quote_ident(substring(TG_TABLE_NAME from '(.+)_view$'));
BEGIN
   EXECUTE 'INSERT INTO ' || tbl || ' SELECT ($1).*'
   USING NEW;

   RETURN NEW;  -- don't return NULL unless you know what you're doing
END
$func$ LANGUAGE plpgsql;

Déclencheurs :

CREATE TRIGGER trg_instead_up
INSTEAD OF UPDATE ON a_view
FOR EACH ROW EXECUTE PROCEDURE f_trg_up();

CREATE TRIGGER trg_instead_ins
INSTEAD OF INSERT ON a_view
FOR EACH ROW EXECUTE PROCEDURE f_trg_ins();

Violon SQL démontrant INSERT et UPDATE .

Points majeurs

  • Incluez le nom du schéma pour rendre la référence de table non ambiguë. Il peut y avoir plusieurs instances du même nom de table dans la même base de données dans plusieurs schémas !

  • Requête pg_attribute au lieu de information_schema.columns . C'est moins portable, mais beaucoup plus rapide et me permet d'utiliser la table-OID.

    • Comment vérifier si une table existe dans un schéma donné
  • Les noms de table ne sont PAS sûrs contre SQLi lorsqu'ils sont traités comme des chaînes, comme dans la création de requêtes pour SQL dynamique. Échappez-vous avec quote_ident() ou format() ou avec un type d'identificateur d'objet. Cela inclut les variables de fonction de déclenchement spéciales TG_TABLE_SCHEMA et TG_TABLE_NAME !

  • Convertir en type d'identifiant d'objet regclass pour affirmer que le nom de la table est valide et obtenir l'OID pour la recherche dans le catalogue.

  • Utilisez éventuellement format() pour créer la chaîne de requête dynamique en toute sécurité.

  • Pas besoin de SQL dynamique pour la première requête sur les tables du catalogue. Plus rapide, plus simple.

  • Utilisez RETURN NEW au lieu de RETURN NULL dans ces fonctions de déclenchement à moins que vous ne sachiez ce que vous faites. (NULL annulerait le INSERT pour la ligne actuelle.)

  • Cette version simple suppose que chaque table (et vue) a une colonne unique nommée id . Une version plus sophistiquée pourrait utiliser la clé primaire de manière dynamique.

  • La fonction pour UPDATE permet aux colonnes de la vue et de la table d'être dans n'importe quel ordre , tant que l'ensemble est le même. La fonction pour INSERT s'attend à ce que les colonnes de la vue et de la table soient dans le ordre identique . Si vous souhaitez autoriser un ordre arbitraire, ajoutez une liste de définition de colonne à INSERT commande, comme avec UPDATE .

  • La version mise à jour couvre également les modifications apportées à l'id colonne en utilisant OLD en plus.