Comment accéder au champ NOUVEAU ou ANCIEN étant donné uniquement le nom du champ?

8

J'écris un déclencheur de validation. Le déclencheur doit valider que la somme d'un tableau est égale à un autre champ. Étant donné que j'ai de nombreuses instances de cette validation, je veux écrire une seule procédure et créer plusieurs déclencheurs, chacun avec un ensemble différent de champs à vérifier.

Par exemple, j'ai le schéma suivant:

CREATE TABLE daily_reports(
     start_on date
   , show_id uuid
   , primary key(start_on, show_id)

     -- _graph are hourly values, while _count is total for the report
   , impressions_count bigint not null
   , impressions_graph bigint[] not null

   -- interactions_count, interactions_graph
   -- twitter_interactions_count, twitter_interactions_graph
);

La validation doit le confirmer impressions_count = sum(impressions_graph).

Je suis bloqué car je ne sais pas comment accéder dynamiquement à un champ depuis NEWplpgsql:

CREATE FUNCTION validate_sum_of_array_equals_other() RETURNS TRIGGER AS $$
DECLARE
  total bigint;
  array_sum bigint;
BEGIN
  -- TG_NARGS = 2
  -- TG_ARGV[0] = 'impressions_count'
  -- TG_ARGV[1] = 'impressions_graph'

  -- How to access impressions_count and impressions_graph from NEW?
  RETURN NEW;
END
$$ LANGUAGE plpgsql;

CREATE TRIGGER validate_daily_reports_impressions
ON daily_reports BEFORE INSERT OR UPDATE
FOR EACH ROW EXECUTE
  validate_sum_of_array_equals_other('impressions_count', 'impressions_graph');

J'ai essayé d' exécuter des commandes dynamiques en faisant EXECUTE 'SELECT $1 FROM NEW' INTO total USING TG_ARGV[0], mais PL / PGsql se plaint que NEW est une relation inconnue.

Je cible spécifiquement PostgreSQL 9.1.

François Beausoleil
la source
Cross posté sur la liste de diffusion générale PostgreSQL postgresql.org/message-id/…
François Beausoleil
AFAIK la seule façon d'accéder dynamiquement aux champs à l'intérieur NEWpour le moment est d'utiliser hstore(NEW)puis d'accéder aux champs en tant que hstorevaleurs saisies par nom de colonne. Ce qui est nul, car ils sont tous castés textet si vous souhaitez travailler avec eux dans leur type d'origine, vous devez les restituer. Alternativement, vous pouvez écrire un déclencheur dans un autre langage procédural comme PL / Python qui prend mieux en charge l'accès aux enregistrements dynamiques.
Craig Ringer
@CraigRinger: Eh bien, non. Tu es trop pessimiste aujourd'hui. Il y a un moyen avec SQL dynamique.
Erwin Brandstetter

Réponses:

14

En fait, comme il NEWs'agit d'un type composite bien défini, vous pouvez simplement accéder à n'importe quelle colonne avec une notation d'attribut simple et simple. SQL lui-même ne permet pas les identifiants dynamiques (noms de table ou de colonne, etc.). Mais vous pouvez utiliser du SQL dynamique avecEXECUTE dans une fonction PL / pgSQL.

Démo

CREATE OR REPLACE FUNCTION trg_demo1()
  RETURNS TRIGGER AS
$func$
DECLARE
   _col_value text;
   _col_name  text := quote_ident(TG_ARGV[0]);  -- escape identifier
BEGIN
   EXECUTE format('SELECT ($1).%s::text', _col_name)
   USING NEW
   INTO  _col_value;

   -- do something with _col_value ...

   RAISE NOTICE 'It works. The value of NEW.% is >>%<<.', _col_name, _col_value;

   RETURN NEW;
END
$func$ LANGUAGE plpgsql;

La conversion en textest facultative. L'utiliser, car cela fonctionne universellement. Si vous connaissez le type, vous pouvez travailler sans lancer ...

Utilisation format()avec %s, car l'identifiant est déjà échappé à ce stade.
Sinon, utilisez format()avec %Ipour vous protéger contre l'injection SQL.

Alternativement , dans Postgres 9.3 ou version ultérieure, vous pouvez convertir NEWen JSON avec to_json()et accéder aux colonnes en tant que clés:

CREATE OR REPLACE FUNCTION trg_demo2()
  RETURNS TRIGGER AS
$func$
DECLARE
   _col_value text := to_json(NEW) ->> TG_ARGV[0];  -- no need to escape identifier
BEGIN
   RAISE NOTICE 'It works. The value of NEW.% is >>%<<.', TG_ARGV[0], _col_value;
   RETURN NEW;
END
$func$ LANGUAGE plpgsql;

Étant donné que le nom de la colonne n'est pas concaténé dans une chaîne SQL, l'injection SQL n'est pas possible et le nom n'a pas besoin d'être échappé.

db <> joue ici (avec EXCEPTIONau lieu de NOTICEpour rendre l'effet visible).

En relation:

Erwin Brandstetter
la source