sql >> Database >  >> RDS >> PostgreSQL

Hoe update ik alle kolommen met INSERT ... ON CONFLICT ...?

De UPDATE syntaxis vereist om doelkolommen expliciet te benoemen. Mogelijke redenen om dat te vermijden:

  • U heeft veel kolommen en wilt alleen de syntaxis inkorten.
  • Je weet het niet weet niet kolomnamen behalve de unieke kolom(men).

"All columns" moet "alle kolommen van de doeltabel" betekenen (of in ieder geval "voorloopkolommen van de tabel" ) in overeenkomende volgorde en overeenkomend gegevenstype. Anders zou u toch een lijst met doelkolomnamen moeten opgeven.

Testtabel:

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

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

1. DELETE &INSERT in plaats daarvan in één zoekopdracht

Zonder kolomnamen te kennen behalve id .

Werkt alleen voor "alle kolommen van de doeltabel" . Hoewel de syntaxis zelfs werkt voor een leidende subset, worden overtollige kolommen in de doeltabel teruggezet op NULL met DELETE en INSERT .

UPSERT (INSERT ... ON CONFLICT ... ) is nodig om gelijktijdigheids- / vergrendelingsproblemen bij gelijktijdige schrijfbelasting te voorkomen, en alleen omdat er geen algemene manier is om nog niet bestaande rijen in Postgres te vergrendelen (waardevergrendeling ).

Uw speciale eis is alleen van invloed op de UPDATE deel. Mogelijke complicaties zijn niet van toepassing op bestaande rijen worden beïnvloed. Die zitten goed op slot. Om nog meer te vereenvoudigen, kunt u uw zaak terugbrengen tot DELETE en 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;

In het Postgres MVCC-model, een UPDATE is grotendeels hetzelfde als DELETE en INSERT hoe dan ook (behalve enkele hoekgevallen met gelijktijdigheid, HOT-updates en grote kolomwaarden die niet in lijn zijn opgeslagen). Aangezien u toch alle rijen wilt vervangen, verwijdert u de conflicterende rijen vóór de INSERT . Verwijderde rijen blijven vergrendeld totdat de transactie is vastgelegd. De INSERT vindt mogelijk alleen conflicterende rijen voor voorheen niet-bestaande sleutelwaarden als een gelijktijdige transactie deze gelijktijdig invoegt (na de DELETE , maar vóór de INSERT ).

In dit speciale geval verliest u extra kolomwaarden voor betrokken rijen. Geen uitzondering genoemd. Maar als concurrerende zoekopdrachten dezelfde prioriteit hebben, is dat geen probleem:de andere zoekopdracht won voor sommige rijen. Als de andere query een vergelijkbare UPSERT is, is het alternatief om te wachten tot deze transactie is vastgelegd en vervolgens meteen bij te werken. "Winnen" kan een Pyrrusoverwinning zijn.

Over "lege updates":

  • Hoe kan (of kan ik) DISTINCT SELECTEREN op meerdere kolommen?

Nee, mijn vraag moet winnen!

OK, je hebt erom gevraagd:

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;

Hoe?

  • De 1e CTE data levert alleen gegevens op. Zou in plaats daarvan een tafel kunnen zijn.
  • De 2e CTE ups :UPERT. Rijen met conflicterende id worden niet gewijzigd, maar ook vergrendeld .
  • De 3e CTE del verwijdert conflicterende rijen. Ze blijven vergrendeld.
  • De 4e CTE ins voegt hele rijen in . Alleen toegestaan ​​voor dezelfde transactie
  • De laatste SELECT is alleen voor de demo om te laten zien wat er is gebeurd.

Om te controleren op lege updates test (voor en na) met:

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

De (uitgecommentarieerde) controle op eventuele wijzigingen in de rij AND t <> d werkt zelfs met NULL-waarden omdat we twee getypte rijwaarden vergelijken volgens de handleiding:

twee NULL-veldwaarden worden als gelijk beschouwd en een NULL wordt als groter beschouwd dan een niet-NULL

2. Dynamische SQL

Dit werkt ook voor een subset van voorloopkolommen, waarbij bestaande waarden behouden blijven.

De truc is om Postgres de queryreeks met kolomnamen uit de systeemcatalogi dynamisch te laten bouwen en deze vervolgens uit te voeren.

Zie gerelateerde antwoorden voor code:

  • Update meerdere kolommen in een triggerfunctie in plpgsql

  • Bulkupdate van alle kolommen

  • SQL update velden van een tabel van velden van een andere



  1. MySQL:Hoe rijen te kopiëren, maar een paar velden te wijzigen?

  2. mySQL-gegevensbron op Visual Studio 2012

  3. Verschil tussen datetime en timestamp in sqlserver?

  4. Google BigQuery ODBC-stuurprogramma