sql >> Databasteknik >  >> RDS >> PostgreSQL

Uppdatera flera kolumner i en triggerfunktion i plpgsql

Även om @Garys svar är tekniskt korrekt, misslyckas han med att nämna att PostgreSQL gör stödja detta formulär:

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

Läs manualen på UPDATE ännu en gång.

Det är fortfarande svårt att få det gjort med dynamisk SQL. Eftersom du inte specificerade, antar jag ett enkelt fall där vyer består av samma kolumner som deras underliggande tabeller.

CREATE VIEW tbl_view AS SELECT * FROM tbl;

Problem

  • Specialposten NEW är inte synligt i EXECUTE . Jag skickar NEW som en enda parameter med USING sats av EXECUTE .

  • Som diskuterats, UPDATE med listform behöver individuella värden . Jag använder ett underval för att dela upp posten i enskilda kolumner:

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

    (Parentes runt $1 är inte valfria.) Detta tillåter mig att helt enkelt använda två kolumnlistor byggda med string_agg() från katalogtabellen:en med och en utan tabellkvalifikation.

  • Det är inte möjligt att tilldela ett radvärde som helhet till enskilda kolumner. Manualen:

    Enligt standarden kan källvärdet för en underlista i parentes med målkolumnnamn vara vilket radvärderat uttryck som helst som ger korrekt antal kolumner. PostgreSQL tillåter endast att källvärdet är en radkonstruktor eller en sub-SELECT .

  • INSERT implementeras enklare. Förutsatt att strukturen för vyn och tabellen är identiska utelämnar jag kolumndefinitionslistan. (Kan förbättras, se nedan.)

Lösning

Jag gjorde ett antal uppdateringar av ditt tillvägagångssätt för att få det att lysa.

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

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

Utlösare:

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 demonstrerar INSERT och UPDATE .

Huvudpunkter

  • Inkludera schemanamnet för att göra tabellreferensen entydig. Det kan finnas flera instanser av samma tabellnamn i samma databas i flera scheman!

  • Fråga pg_attribute istället för information_schema.columns . Det är mindre bärbart, men mycket snabbare och låter mig använda tabell-OID.

    • Hur man kontrollerar om en tabell finns i ett givet schema
  • Tabellnamn är INTE säkra mot SQLi när de hanteras som strängar som i att bygga frågor för dynamisk SQL. Escape med quote_ident() eller format() eller med en objektidentifierartyp. Detta inkluderar de speciella triggerfunktionsvariablerna TG_TABLE_SCHEMA och TG_TABLE_NAME !

  • Casta till objektidentifieraren typ regclass för att hävda att tabellnamnet är giltigt och hämta OID för katalogsökningen.

  • Använd valfritt format() för att bygga den dynamiska frågesträngen på ett säkert sätt.

  • Inget behov av dynamisk SQL för den första frågan i katalogtabellerna. Snabbare, enklare.

  • Använd RETURN NEW istället för RETURN NULL i dessa triggerfunktioner om du inte vet vad du gör. (NULL skulle avbryta INSERT för den aktuella raden.)

  • Denna enkla version förutsätter att varje tabell (och vy) har en unik kolumn som heter id . En mer sofistikerad version kan använda primärnyckeln dynamiskt.

  • Funktionen för UPDATE tillåter vykolumnerna och tabellen att vara i valfri ordning , så länge uppsättningen är densamma. Funktionen för INSERT förväntar sig att vykolumnerna och tabellen är i identisk ordning . Om du vill tillåta godtycklig ordning, lägg till en kolumndefinitionslista till INSERT kommandot, precis som med UPDATE .

  • Uppdaterad version täcker även ändringar av id kolumnen genom att använda OLD dessutom.



  1. Konfigurera tillgänglighetsgruppanslutning

  2. MySQL-pivottabellsfråga med dynamiska kolumner

  3. SQL Server NÅGON operatör förklaras

  4. Hur hanterar man bäst historiska uppslagsvärden i en databas?