PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Aktualisieren Sie mehrere Spalten in einer Triggerfunktion in plpgsql

Während @Garys Antwort technisch korrekt ist, erwähnt er nicht, dass PostgreSQL dies tut dieses Formular unterstützen:

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

Lesen Sie das Handbuch unter UPDATE noch einmal.

Es ist immer noch schwierig, mit dynamischem SQL fertig zu werden. Da Sie nichts angegeben haben, gehe ich von einem einfachen Fall aus, in dem Ansichten aus denselben Spalten wie ihre zugrunde liegenden Tabellen bestehen.

CREATE VIEW tbl_view AS SELECT * FROM tbl;

Probleme

  • Der spezielle Datensatz NEW ist in EXECUTE nicht sichtbar . Ich übergebe NEW als einzelner Parameter mit dem USING Klausel von EXECUTE .

  • Wie besprochen, UPDATE mit list-form benötigt individuelle Werte . Ich verwende eine Unterauswahl, um den Datensatz in einzelne Spalten aufzuteilen:

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

    (Klammern um $1 sind nicht optional.) Dadurch kann ich einfach zwei Spaltenlisten verwenden, die mit string_agg() erstellt wurden aus der Katalogtabelle:eine mit und eine ohne Tabellenkennzeichnung.

  • Es ist nicht möglich, einzelnen Spalten einen Zeilenwert als Ganzes zuzuweisen. Das Handbuch:

    Gemäß dem Standard kann der Quellwert für eine eingeklammerte Unterliste von Zielspaltennamen ein beliebiger zeilenwertiger Ausdruck sein, der die richtige Anzahl von Spalten ergibt. PostgreSQL lässt nur zu, dass der Quellwert ein Zeilenkonstruktor oder ein Unter-SELECT ist .

  • INSERT ist einfacher implementiert. Unter der Annahme, dass die Struktur von Ansicht und Tabelle identisch sind, lasse ich die Spaltendefinitionsliste weg. (Kann verbessert werden, siehe unten.)

Lösung

Ich habe eine Reihe von Aktualisierungen an Ihrem Ansatz vorgenommen, um ihn zum Glänzen zu bringen.

Auslösefunktion für 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;

Auslösefunktion für 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;

Auslöser:

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

SQL-Geige Demonstration von INSERT und UPDATE .

Wichtige Punkte

  • Schließen Sie den Schemanamen ein, um die Tabellenreferenz eindeutig zu machen. Es kann mehrere Instanzen desselben Tabellennamens in derselben Datenbank in mehreren Schemas geben!

  • Fragen Sie pg_attribute ab anstelle von information_schema.columns . Das ist weniger portabel, aber viel schneller und erlaubt mir, die Tabellen-OID zu verwenden.

    • Überprüfen, ob eine Tabelle in einem bestimmten Schema existiert
  • Tabellennamen sind NICHT sicher gegen SQLi wenn sie als Zeichenfolgen behandelt werden, wie beim Erstellen von Abfragen für dynamisches SQL. Entkommen Sie mit quote_ident() oder format() oder mit einem Objektidentifizierertyp. Dazu gehören die speziellen Trigger-Funktionsvariablen TG_TABLE_SCHEMA und TG_TABLE_NAME !

  • In den Objektidentifizierertyp regclass umwandeln um zu bestätigen, dass der Tabellenname gültig ist, und um die OID für die Katalogsuche zu erhalten.

  • Verwenden Sie optional format() um die dynamische Abfragezeichenfolge sicher zu erstellen.

  • Keine Notwendigkeit für dynamisches SQL für die erste Abfrage der Katalogtabellen. Schneller, einfacher.

  • Verwenden Sie RETURN NEW statt RETURN NULL in diesen Triggerfunktionen, es sei denn, Sie wissen, was Sie tun. (NULL würde INSERT abbrechen für die aktuelle Zeile.)

  • Diese einfache Version geht davon aus, dass jede Tabelle (und Ansicht) eine eindeutige Spalte namens id hat . Eine anspruchsvollere Version könnte den Primärschlüssel dynamisch verwenden.

  • Die Funktion für UPDATE ermöglicht die beliebige Reihenfolge der Spalten von Ansicht und Tabelle , solange die Menge gleich ist. Die Funktion für INSERT erwartet, dass die Spalten von Ansicht und Tabelle in identischer Reihenfolge sind . Wenn Sie eine beliebige Reihenfolge zulassen möchten, fügen Sie dem INSERT eine Spaltendefinitionsliste hinzu Befehl, genau wie bei UPDATE .

  • Die aktualisierte Version deckt auch Änderungen an der id ab Spalte mit OLD zusätzlich.