sql >> Databasteknik >  >> RDS >> PostgreSQL

Hur uppdaterar man alla kolumner med INSERT ... ON CONFLICT ...?

UPDATE syntax kräver för att uttryckligen namnge målkolumner. Möjliga skäl att undvika det:

  • Du har många kolumner och vill bara förkorta syntaxen.
  • Du vet inte kolumnnamn förutom de unika kolumnerna.

"All columns" måste betyda "alla kolumner i måltabellen" (eller åtminstone "ledande kolumner i tabellen" ) i matchande ordning och matchande datatyp. Annars måste du ändå tillhandahålla en lista med målkolumnnamn.

Testtabell:

CREATE TABLE tbl (
   id    int PRIMARY KEY
 , text  text
 , extra text
);

INSERT INTO tbl AS t
VALUES (1, 'foo')
     , (2, 'bar');

1. DELETE &INSERT i en enda fråga istället

Utan att känna till några kolumnnamn förutom id .

Fungerar endast för "alla kolumner i måltabellen" . Även om syntaxen till och med fungerar för en ledande delmängd, skulle överflödiga kolumner i måltabellen återställas till NULL med DELETE och INSERT .

UPSERT (INSERT ... ON CONFLICT ... ) behövs för att undvika samtidighets-/låsningsproblem under samtidig skrivbelastning, och bara för att det inte finns något allmänt sätt att låsa rader som ännu inte finns i Postgres (värdelåsning ).

Dina speciella krav påverkar bara UPDATE del. Eventuella komplikationer gäller inte där existerar rader påverkas. De är ordentligt låsta. För att förenkla något mer kan du minska ditt ärende till DELETE och INSERT :

WITH data(id) AS (              -- Only 1st column gets explicit name!
   VALUES
      (1, 'foo_upd', 'a')       -- changed
    , (2, 'bar', 'b')           -- unchanged
    , (3, 'baz', 'c')           -- new
   )
, del AS (
   DELETE FROM tbl AS t
   USING  data d
   WHERE  t.id = d.id
   -- AND    t <> d              -- optional, to avoid empty updates
   )                             -- only works for complete rows
INSERT INTO tbl AS t
TABLE  data                      -- short for: SELECT * FROM data
ON     CONFLICT (id) DO NOTHING
RETURNING t.id;

I Postgres MVCC-modellen, en UPDATE är i stort sett detsamma som DELETE och INSERT i alla fall (förutom vissa hörnfall med samtidighet, HETA uppdateringar och stora kolumnvärden lagrade utanför linjen). Eftersom du ändå vill ersätta alla rader, ta bara bort motstridiga rader före INSERT . Raderade rader förblir låsta tills transaktionen genomförs. INSERT kan bara hitta motstridiga rader för tidigare icke-existerande nyckelvärden om en samtidig transaktion råkar infoga dem samtidigt (efter DELETE , men före INSERT ).

Du skulle förlora ytterligare kolumnvärden för berörda rader i detta speciella fall. Inget undantag togs upp. Men om konkurrerande frågor har samma prioritet är det knappast något problem:den andra frågan vann för några rader. Dessutom, om den andra frågan är en liknande UPSERT, är dess alternativ att vänta på att den här transaktionen genomförs och sedan uppdateras direkt. "Att vinna" kan vara en pyrrhusseger.

Om "tomma uppdateringar":

  • Hur väljer jag (eller kan jag) DISTINCT på flera kolumner?

Nej, min fråga måste vinna!

OK, du bad om det:

WITH data(id) AS (                   -- Only 1st column gets explicit name!
   VALUES                            -- rest gets default names "column2", etc.
     (1, 'foo_upd', NULL)              -- changed
   , (2, 'bar', NULL)                  -- unchanged
   , (3, 'baz', NULL)                  -- new
   , (4, 'baz', NULL)                  -- new
   )
, ups AS (
   INSERT INTO tbl AS t
   TABLE  data                       -- short for: SELECT * FROM data
   ON     CONFLICT (id) DO UPDATE
   SET    id = t.id
   WHERE  false                      -- never executed, but locks the row!
   RETURNING t.id
   )
, del AS (
   DELETE FROM tbl AS t
   USING  data     d
   LEFT   JOIN ups u USING (id)
   WHERE  u.id IS NULL               -- not inserted !
   AND    t.id = d.id
   -- AND    t <> d                  -- avoid empty updates - only for full rows
   RETURNING t.id
   )
, ins AS (
   INSERT INTO tbl AS t
   SELECT *
   FROM   data
   JOIN   del USING (id)             -- conflict impossible!
   RETURNING id
   )
SELECT ARRAY(TABLE ups) AS inserted  -- with UPSERT
     , ARRAY(TABLE ins) AS updated   -- with DELETE & INSERT;

Hur?

  • Första CTE data ger bara data. Kan vara ett bord istället.
  • Den andra CTE ups :UPSERT. Rader med konflikt id inte ändras, utan också låsta .
  • Den tredje CTE del tar bort motstridiga rader. De förblir låsta.
  • Den fjärde CTE ins infogar hela rader . Endast tillåtet för samma transaktion
  • Det sista VALET är endast för demon för att visa vad som hände.

För att leta efter tomma uppdateringar testa (före och efter) med:

SELECT ctid, * FROM tbl; -- did the ctid change?

Den (kommenterade) kontrollera för eventuella ändringar i raden AND t <> d fungerar även med NULL-värden eftersom vi jämför två inskrivna radvärden enligt manualen:

två NULL-fältvärden anses vara lika, och en NULL anses vara större än en icke-NULL

2. Dynamisk SQL

Detta fungerar även för en delmängd av ledande kolumner och bevarar befintliga värden.

Tricket är att låta Postgres bygga frågesträngen med kolumnnamn från systemkatalogerna dynamiskt och sedan köra den.

Se relaterade svar för kod:

  • Uppdatera flera kolumner i en triggerfunktion i plpgsql

  • Massuppdatering av alla kolumner

  • SQL uppdatera fält i en tabell från fält i en annan



  1. TO_TIMESTAMP() Funktion i Oracle

  2. Hur man kör Opatch i icke interaktiv form

  3. Rekommenderade Intel-processorer för SQL Server 2014-arbetsbelastningar

  4. Matcha utbud med efterfrågan – lösningar, del 1