Fejlen får du:
ON CONFLICT DO UPDATE-kommandoen kan ikke påvirke rækken en anden gang
angiver, at du forsøger at forskyde den samme række mere end én gang i en enkelt kommando. Med andre ord:du har duper på (name, url, email)
i din VALUES
liste. Fold dubletter (hvis det er en mulighed), og det burde virke. Men du bliver nødt til at beslutte, hvilken række du vil vælge fra hvert sæt 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;
Da vi bruger en fritstående VALUES
udtryk nu, skal du tilføje eksplicitte typecasts for ikke-standardtyper. Ligesom:
VALUES
(timestamptz '2016-03-12 02:47:56+01'
, timestamptz '2016-03-12 02:47:56+01'
, 'n3', 'u3', 'e3')
...
Din timestamptz
kolonner har brug for en eksplicit type cast, mens strengtyperne kan fungere med standard text
. (Du kan stadig caste til varchar(n)
med det samme.)
Der er måder at bestemme, hvilken række der skal vælges fra hvert sæt duper:
- Vælg første række i hver GROUP BY-gruppe?
Du har ret, der er (i øjeblikket) ingen måde at blive ekskluderet på rækker i RETURNING
klausul. Jeg citerer Postgres Wiki:
Bemærk, at RETURNING
synliggør ikke "EXCLUDED.*
" alias fra UPDATE
(kun den generiske "TARGET.*
" alias er synligt der). At gøre det menes at skabe irriterende tvetydighed for de simple, almindelige sager [30] til ringe eller ingen fordel. På et tidspunkt i fremtiden kan vi forfølge en måde at afsløre, hvisRETURNING
-Projicerede tuples blev indsat og opdateret, men dette behøver sandsynligvis ikke at indgå i den første forpligtede iteration af funktionen [31].
Men , bør du ikke opdatere rækker, der ikke formodes at blive opdateret. Tomme opdateringer er næsten lige så dyre som almindelige opdateringer - og kan have utilsigtede bivirkninger. Du behøver strengt taget ikke UPSERT til at begynde med, din sag ligner mere "SELECT eller INSERT". Relateret:
- Er SELECT eller INSERT i en funktion, der er tilbøjelig til løbsforhold?
En en renere måde at indsætte et sæt rækker på ville være med datamodificerende 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 ekstra kompleksitet burde betale for store borde, hvor INSERT
er reglen og SELECT
undtagelsen.
Oprindeligt havde jeg tilføjet en NOT EXISTS
prædikat på den sidste SELECT
for at forhindre dubletter i resultatet. Men det var overflødigt. Alle CTE'er i en enkelt forespørgsel ser de samme øjebliksbilleder af tabeller. Sættet returnerede med ON CONFLICT (name, url, email) DO NOTHING
udelukker gensidigt det sæt, der returneres efter INNER JOIN
på de samme kolonner.
Desværre åbner dette også et lille vindue for en løbstilstand . Hvis ...
- en samtidig transaktion indsætter modstridende rækker
- har ikke forpligtet sig endnu
- men forpligter sig til sidst
... nogle rækker kan gå tabt.
Du kan bare INSERT .. ON CONFLICT DO NOTHING
, efterfulgt af en separat SELECT
forespørgsel for alle rækker - inden for samme transaktion for at overvinde dette. Hvilket igen åbner endnu et lille vindue for en løbstilstand hvis samtidige transaktioner kan begå skrivninger til tabellen mellem INSERT
og SELECT
(i standard READ COMMITTED
isolationsniveau). Kan undgås med REPEATABLE READ
transaktionsisolering (eller strengere). Eller med en (muligvis dyr eller endda uacceptabel) skrivelås på hele bordet. Du kan få enhver adfærd, du har brug for, men der kan være en pris at betale.
Relateret:
- Hvordan bruger man RETURNING med ON CONFLICT i PostgreSQL?
- Returner rækker fra INSERT med ON CONFLICT uden at skulle opdatere