Felet du får:
ON CONFLICT DO UPDATE-kommandot kan inte påverka raden en andra gång
indikerar att du försöker flytta upp samma rad mer än en gång i ett enda kommando. Med andra ord:du har duper på (name, url, email)
i din VALUES
lista. Vik dubbletter (om det är ett alternativ) och det borde fungera. Men du måste bestämma vilken rad du ska välja från varje uppsättning duper.
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;
Eftersom vi använder en fristående VALUES
expression nu måste du lägga till explicita typcasts för icke-standardtyper. Gilla:
VALUES
(timestamptz '2016-03-12 02:47:56+01'
, timestamptz '2016-03-12 02:47:56+01'
, 'n3', 'u3', 'e3')
...
Din timestamptz
kolumner behöver en explicit typavgjutning, medan strängtyperna kan fungera med standard text
. (Du kan fortfarande casta till varchar(n)
direkt.)
Det finns sätt att bestämma vilken rad du ska välja från varje uppsättning duper:
- Välj första raden i varje GROUP BY-grupp?
Du har rätt, det finns (för närvarande) inget sätt att bli utesluten rader i RETURNING
klausul. Jag citerar Postgres Wiki:
Observera att
RETURNING
visar inte "EXCLUDED.*
" alias frånUPDATE
(bara det generiska "TARGET.*
" alias är synligt där). Att göra det tros skapa irriterande tvetydighet för de enkla, vanliga fallen [30] till liten eller ingen nytta. Någon gång i framtiden kan vi komma att försöka avslöja omRETURNING
-Projicerade tupler infogades och uppdaterades, men detta behöver antagligen inte komma in i den första begångna iterationen av funktionen [31].
Men , bör du inte uppdatera rader som inte är tänkta att uppdateras. Tomma uppdateringar är nästan lika dyra som vanliga uppdateringar - och kan ha oavsiktliga biverkningar. Du behöver strikt inte UPSERT till att börja med, ditt fall ser mer ut som "SELECT eller INSERT". Relaterat:
- Är SELECT eller INSERT i en funktion utsatt för tävlingsförhållanden?
En renare sätt att infoga en uppsättning rader skulle vara med datamodifierande CTE:er:
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);
Den extra komplexiteten borde betala för stora tabeller där INSERT
är regeln och SELECT
undantaget.
Ursprungligen hade jag lagt till en NOT EXISTS
predikat på den sista SELECT
för att förhindra dubbletter i resultatet. Men det var överflödigt. Alla CTE i en enda fråga ser samma ögonblicksbilder av tabeller. Uppsättningen returnerade med ON CONFLICT (name, url, email) DO NOTHING
är ömsesidigt uteslutande för uppsättningen som returneras efter INNER JOIN
på samma kolumner.
Tyvärr öppnar detta också ett litet fönster för tävlingsförhållanden . Om ...
- en samtidig transaktion infogar motstridiga rader
- har inte åtagit sig ännu
- men förpliktar sig så småningom
... vissa rader kan gå förlorade.
Du kanske bara INSERT .. ON CONFLICT DO NOTHING
, följt av en separat SELECT
fråga för alla rader - inom samma transaktion för att övervinna detta. Vilket i sin tur öppnar ytterligare ett litet fönster för ett tävlingsförhållande om samtidiga transaktioner kan begå skrivningar till tabellen mellan INSERT
och SELECT
(som standard READ COMMITTED
isoleringsnivå). Kan undvikas med REPEATABLE READ
transaktionsisolering (eller strängare). Eller med ett (möjligen dyrt eller till och med oacceptabelt) skrivlås på hela bordet. Du kan få vilket beteende du behöver, men det kan finnas ett pris att betala.
Relaterat:
- Hur använder man RETURNING med ON CONFLICT i PostgreSQL?
- Returnera rader från INSERT med ON CONFLICT utan att behöva uppdatera