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 deUPDATE
(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