sql >> Database >  >> RDS >> PostgreSQL

Uitgesloten rijen opnemen in RETURNING from INSERT ... ON CONFLICT

De foutmelding die je krijgt:

ON CONFLICT DO UPDATE-opdracht kan de rij niet een tweede keer beïnvloeden

geeft aan dat u dezelfde rij meer dan één keer in één opdracht probeert te upseren. Met andere woorden:je hebt dupes op (name, url, email) in uw VALUES lijst. Vouw duplicaten (als dat een optie is) en het zou moeten werken. Maar je zult moeten beslissen welke rij je kiest uit elke set dupes.

INSERT INTO feeds_person (created, modified, name, url, email)
SELECT DISTINCT ON (name, url, email) *
FROM  (
   VALUES
   ('blah', 'blah', 'blah', 'blah', 'blah')
   -- ... more
   ) v(created, modified, name, url, email)  -- match column list
ON     CONFLICT (name, url, email) DO UPDATE
SET    url = feeds_person.url
RETURNING id;

Aangezien we een vrijstaande VALUES . gebruiken expressie nu, moet u expliciete typecasts toevoegen voor niet-standaardtypen. Vind ik leuk:

VALUES
    (timestamptz '2016-03-12 02:47:56+01'
   , timestamptz '2016-03-12 02:47:56+01'
   , 'n3', 'u3', 'e3')
   ...

Uw timestamptz kolommen hebben een expliciete typecast nodig, terwijl de tekenreekstypen kunnen werken met standaard text . (Je zou nog steeds kunnen casten naar varchar(n) meteen.)

Er zijn manieren om te bepalen welke rij je moet kiezen uit elke set dupes:

  • Selecteer de eerste rij in elke GROUP BY-groep?

Je hebt gelijk, er is (momenteel) geen manier om uitgesloten te krijgen rijen in de RETURNING clausule. Ik citeer de Postgres Wiki:

Merk op dat RETURNING maakt de "EXCLUDED.* . niet zichtbaar " alias van de UPDATE (alleen de generieke "TARGET.* " alias is daar zichtbaar). Er wordt aangenomen dat dit vervelende ambiguïteit creëert voor de eenvoudige, veelvoorkomende gevallen [30] met weinig tot geen voordeel. Op een bepaald moment in de toekomst zullen we misschien een manier zoeken om bloot te leggen alsRETURNING -geprojecteerde tuples zijn ingevoegd en bijgewerkt, maar dit hoeft waarschijnlijk niet in de eerste vastgelegde iteratie van de functie [31] te komen.

Echter , moet u geen rijen bijwerken die niet zouden moeten worden bijgewerkt. Lege updates zijn bijna net zo duur als reguliere updates - en kunnen onbedoelde bijwerkingen hebben. Om te beginnen heb je UPSERT niet echt nodig, je case lijkt meer op "SELECT or INSERT". Gerelateerd:

  • Is SELECT of INSERT in een functie die vatbaar is voor race-omstandigheden?

Eén een schonere manier om een ​​reeks rijen in te voegen zou zijn met gegevensmodificerende CTE's:

WITH val AS (
   SELECT DISTINCT ON (name, url, email) *
   FROM  (
      VALUES 
      (timestamptz '2016-1-1 0:0+1', timestamptz '2016-1-1 0:0+1', 'n', 'u', 'e')
    , ('2016-03-12 02:47:56+01', '2016-03-12 02:47:56+01', 'n1', 'u3', 'e3')
      -- more (type cast only needed in 1st row)
      ) v(created, modified, name, url, email)
   )
, ins AS (
   INSERT INTO feeds_person (created, modified, name, url, email)
   SELECT created, modified, name, url, email FROM val
   ON     CONFLICT (name, url, email) DO NOTHING
   RETURNING id, name, url, email
   )
SELECT 'inserted' AS how, id FROM ins  -- inserted
UNION  ALL
SELECT 'selected' AS how, f.id         -- not inserted
FROM   val v
JOIN   feeds_person f USING (name, url, email);

De toegevoegde complexiteit zou moeten betalen voor grote tabellen waar INSERT is de regel en SELECT de uitzondering.

Oorspronkelijk had ik een NOT EXISTS . toegevoegd predikaat op de laatste SELECT om duplicaten in het resultaat te voorkomen. Maar dat was overbodig. Alle CTE's van een enkele zoekopdracht zien dezelfde snapshots van tabellen. De set keerde terug met ON CONFLICT (name, url, email) DO NOTHING is wederzijds exclusief voor de set die wordt geretourneerd na de INNER JOIN op dezelfde kolommen.

Helaas opent dit ook een klein venster voor een raceconditie . Als ...

  • een gelijktijdige transactie voegt conflicterende rijen in
  • heeft nog niet toegezegd
  • maar verbindt zich uiteindelijk

... sommige rijen kunnen verloren gaan.

Je zou gewoon INSERT .. ON CONFLICT DO NOTHING , gevolgd door een aparte SELECT query voor alle rijen - binnen dezelfde transactie om dit te verhelpen. Wat op zijn beurt weer een klein venster opent voor een raceconditie als gelijktijdige transacties schrijfacties naar de tabel kunnen vastleggen tussen INSERT en SELECT (standaard READ COMMITTED isolatieniveau). Kan worden vermeden met REPEATABLE READ transactie-isolatie (of strenger). Of met een (mogelijk duur of zelfs onacceptabel) schrijfslot op de hele tafel. Je kunt elk gewenst gedrag krijgen, maar er kan een prijs voor betaald worden.

Gerelateerd:

  • Hoe RETURNING gebruiken met ON CONFLICT in PostgreSQL?
  • Retourneer rijen van INSERT met ON CONFLICT zonder te updaten



  1. Fix "ERROR 3942 (HY000):elke rij van een VALUES-clausule moet ten minste één kolom hebben" bij gebruik van de VALUES-instructie in MySQL

  2. 3 manieren om het jaar, de maand en de dag te scheiden van een datum in MariaDB

  3. Opslaggroottes voor MySQL TEXT-gegevenstypen begrijpen

  4. Jaren toevoegen aan een datum in PostgreSQL