Wie kann ich auf das Feld NEU oder ALT zugreifen, wenn nur der Name des Feldes angegeben wird?

8

Ich schreibe einen Validierungsauslöser. Der Trigger muss überprüfen, ob die Summe eines Arrays einem anderen Feld entspricht. Da ich viele Instanzen dieser Validierung habe, möchte ich eine einzelne Prozedur schreiben und mehrere Trigger mit jeweils unterschiedlichen Feldern erstellen, die überprüft werden sollen.

Zum Beispiel habe ich das folgende Schema:

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
);

Die Validierung muss dies bestätigen impressions_count = sum(impressions_graph).

Ich stecke fest, weil ich nicht weiß, wie ich NEWvon plpgsql aus dynamisch auf ein Feld zugreifen kann:

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');

Ich habe versucht , dynamisches Kommando ausführen , indem Sie EXECUTE 'SELECT $1 FROM NEW' INTO total USING TG_ARGV[0], aber PL / pgSQL beklagt , dass NEU eine unbekannte Beziehung ist.

Ich ziele speziell auf PostgreSQL 9.1 ab.

François Beausoleil
quelle
Cross auf der allgemeinen PostgreSQL-Mailingliste postgresql.org/message-id/… veröffentlicht
François Beausoleil
AFAIK Die einzige Möglichkeit, dynamisch auf Felder zuzugreifen, besteht darin, die Felder als durch den Spaltennamen eingegebene Werte NEWzu verwenden hstore(NEW)und dann darauf zuzugreifen hstore. Was scheiße ist, denn dann sind sie alle besetzt textund wenn Sie mit ihnen in ihrem ursprünglichen Typ arbeiten möchten, müssen Sie sie zurückwerfen. Alternativ können Sie einen Trigger in einer anderen prozeduralen Sprache wie PL / Python schreiben, die den dynamischen Datensatzzugriff besser unterstützt.
Craig Ringer
@CraigRinger: Nun, nein. Sie sind heute zu pessimistisch. Es gibt einen Weg mit dynamischem SQL.
Erwin Brandstetter

Antworten:

14

Da NEWes sich um einen genau definierten zusammengesetzten Typ handelt, können Sie einfach auf jede Spalte mit einfacher Attributnotation zugreifen. SQL selbst erlaubt keine dynamischen Bezeichner (Tabellen- oder Spaltennamen usw.). Sie können jedoch dynamisches SQL mitEXECUTE in einer PL / pgSQL-Funktion verwenden.

Demo

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;

Die Besetzung textist optional. Verwenden Sie es, weil es universell funktioniert. Wenn Sie wissen , die Art, können Sie ohne Gießen arbeiten ...

Verwendung format()mit %s, da der Bezeichner zu diesem Zeitpunkt bereits maskiert ist.
Andernfalls verwenden Sie format()mit %I, um sich vor SQL-Injection zu schützen.

Alternativ können Sie in Postgres 9.3 oder höher NEWin JSON konvertieren to_json()und auf Spalten als Schlüssel zugreifen:

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;

Da der Spaltenname nicht mit einer SQL-Zeichenfolge verknüpft ist, ist eine SQL-Injection nicht möglich und der Name muss nicht maskiert werden.

db <> hier fummeln (mit EXCEPTIONanstatt NOTICEden Effekt sichtbar zu machen).

Verbunden:

Erwin Brandstetter
quelle