sql >> Database >  >> RDS >> PostgreSQL

Update meerdere kolommen in een triggerfunctie in plpgsql

Hoewel het antwoord van @Gary technisch correct is, vermeldt hij niet dat PostgreSQL wel ondersteun dit formulier:

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

Lees de handleiding op UPDATE nog een keer.

Het is nog steeds lastig om zijn gedaan te krijgen met dynamische SQL. Aangezien u niet specificeerde, ga ik uit van een eenvoudig geval waarin weergaven uit dezelfde kolommen bestaan ​​​​als hun onderliggende tabellen.

CREATE VIEW tbl_view AS SELECT * FROM tbl;

Problemen

  • Het speciale record NEW is niet zichtbaar in EXECUTE . Ik ben geslaagd voor NEW als een enkele parameter met de USING clausule van EXECUTE .

  • Zoals besproken, UPDATE met lijstvorm vereist individuele waarden . Ik gebruik een subselectie om het record in afzonderlijke kolommen te splitsen:

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

    (haakjes rond $1 zijn niet optioneel.) Hierdoor kan ik eenvoudig twee kolomlijsten gebruiken die zijn gemaakt met string_agg() uit de catalogustabel:een met en een zonder tafelkwalificatie.

  • Het is niet mogelijk om een ​​rijwaarde als geheel toe te wijzen aan afzonderlijke kolommen. De handleiding:

    Volgens de standaard kan de bronwaarde voor een tussen haakjes geplaatste sublijst van doelkolomnamen elke expressie met rijwaarde zijn die het juiste aantal kolommen oplevert. PostgreSQL staat alleen toe dat de bronwaarde een rijconstructor of een sub-SELECT is .

  • INSERT eenvoudiger wordt uitgevoerd. Ervan uitgaande dat de structuur van de weergave en de tabel identiek zijn, laat ik de lijst met kolomdefinities weg. (Kan verbeterd worden, zie hieronder.)

Oplossing

Ik heb een aantal updates voor je aanpak gemaakt om het te laten schitteren.

Triggerfunctie voor 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;

Triggerfunctie voor 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;

Triggers:

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 demonstreren INSERT en UPDATE .

Belangrijkste punten

  • Voeg de schemanaam toe om de tabelverwijzing ondubbelzinnig te maken. Er kunnen meerdere instanties van dezelfde tabelnaam in dezelfde database in meerdere schema's voorkomen!

  • Zoekopdracht pg_attribute in plaats van information_schema.columns . Dat is minder draagbaar, maar veel sneller en stelt me ​​in staat om de table-OID te gebruiken.

    • Hoe te controleren of een tabel in een bepaald schema bestaat
  • Tabelnamen zijn NIET veilig tegen SQLi wanneer behandeld als tekenreeksen, zoals bij het bouwen van query's voor dynamische SQL. Ontsnap met quote_ident() of format() of met een object-identifer type. Dit omvat de speciale triggerfunctievariabelen TG_TABLE_SCHEMA en TG_TABLE_NAME !

  • Cast naar het object-ID type regclass om te bevestigen dat de tabelnaam geldig is en de OID op te halen voor het opzoeken van de catalogus.

  • Gebruik optioneel format() om de dynamische queryreeks veilig te bouwen.

  • Dynamische SQL is niet nodig voor de eerste query op de catalogustabellen. Sneller, eenvoudiger.

  • Gebruik RETURN NEW in plaats van RETURN NULL in deze triggerfuncties, tenzij u weet wat u doet. (NULL zou de INSERT . annuleren voor de huidige rij.)

  • Deze eenvoudige versie gaat ervan uit dat elke tabel (en weergave) een unieke kolom heeft met de naam id . Een meer geavanceerde versie zou de primaire sleutel dynamisch kunnen gebruiken.

  • De functie voor UPDATE staat toe dat de weergavekolommen en tabel in elke volgorde staan , zolang de set hetzelfde is. De functie voor INSERT verwacht dat de kolommen van weergave en tabel in identieke volgorde staan . Als u willekeurige volgorde wilt toestaan, voegt u een kolomdefinitielijst toe aan de INSERT commando, net als bij UPDATE .

  • Bijgewerkte versie omvat ook wijzigingen aan de id kolom met behulp van OLD bovendien.



  1. Hoe gebruik ik een globale tijdelijke tabel in de Oracle-procedure?

  2. bij het invoegen van een Perzisch teken in oracle db zie ik het vraagteken

  3. Top drie trends die van invloed zijn op DBA's die verantwoordelijk zijn voor SQL Server-bewaking

  4. Databasestructuur synchroniseren tussen applicaties