PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Aggiorna più colonne in una funzione trigger in plpgsql

Sebbene la risposta di @Gary sia tecnicamente corretta, non menziona che PostgreSQL lo fa supporta questo modulo:

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

Leggi il manuale su UPDATE ancora una volta.

È ancora difficile farlo con SQL dinamico. Dal momento che non hai specificato, presumo un caso semplice in cui le viste sono costituite dalle stesse colonne delle tabelle sottostanti.

CREATE VIEW tbl_view AS SELECT * FROM tbl;

Problemi

  • Il record speciale NEW non è visibile all'interno di EXECUTE . Passo NEW come parametro singolo con USING clausola di EXECUTE .

  • Come discusso, UPDATE con list-form ha bisogno di valori individuali . Uso una sottoselezione per dividere il record in singole colonne:

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

    (Tra parentesi intorno a $1 non sono opzionali.) Questo mi permette di usare semplicemente due elenchi di colonne costruiti con string_agg() dalla tabella di catalogo:una con e una senza qualifica di tabella.

  • Non è possibile assegnare un valore di riga nel suo insieme a singole colonne. Il manuale:

    Secondo lo standard, il valore di origine per un sottoelenco tra parentesi di nomi di colonne di destinazione può essere qualsiasi espressione con valori di riga che fornisca il numero corretto di colonne. PostgreSQL consente solo al valore di origine di essere un costruttore di riga o un sotto-SELECT .

  • INSERT è implementato in modo più semplice. Supponendo che la struttura della vista e della tabella siano identiche, ometto l'elenco delle definizioni delle colonne. (Può essere migliorato, vedi sotto.)

Soluzione

Ho apportato una serie di aggiornamenti al tuo approccio per farlo brillare.

Funzione di attivazione per 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;

Funzione di trigger per 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;

Trigger:

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 Fiddle dimostrando INSERT e UPDATE .

Punti principali

  • Includere il nome dello schema per rendere univoco il riferimento alla tabella. Possono esserci più istanze dello stesso nome di tabella nello stesso database in più schemi!

  • Interroga pg_attribute invece di information_schema.columns . È meno portatile, ma molto più veloce e mi permette di usare il table-OID.

    • Come verificare se esiste una tabella in un determinato schema
  • I nomi delle tabelle NON sono sicuri contro SQLi se gestiti come stringhe come nella creazione di query per SQL dinamico. Esci con quote_ident() o format() o con un tipo di identificatore di oggetto. Ciò include le variabili della funzione trigger speciale TG_TABLE_SCHEMA e TG_TABLE_NAME !

  • Trasmetti al tipo di identificatore oggetto regclass per affermare che il nome della tabella è valido e ottenere l'OID per la ricerca nel catalogo.

  • Facoltativamente usa format() per creare la stringa di query dinamica in modo sicuro.

  • Non c'è bisogno di SQL dinamico per la prima query sulle tabelle del catalogo. Più veloce, più semplice.

  • Usa RETURN NEW invece di RETURN NULL in queste funzioni di attivazione a meno che tu non sappia cosa stai facendo. (NULL cancellerebbe il INSERT per la riga corrente.)

  • Questa versione semplice presuppone che ogni tabella (e vista) abbia una colonna univoca denominata id . Una versione più sofisticata potrebbe utilizzare la chiave primaria in modo dinamico.

  • La funzione per UPDATE consente alle colonne di visualizzazione e tabella di essere in qualsiasi ordine , purché il set sia lo stesso. La funzione per INSERT prevede che le colonne della vista e della tabella siano in ordine identico . Se vuoi consentire un ordine arbitrario, aggiungi un elenco di definizioni di colonna a INSERT comando, proprio come con UPDATE .

  • La versione aggiornata copre anche le modifiche all'id colonna utilizzando OLD in aggiunta.