Zobrazí se chyba:
ON CONFLICT DO UPDATE příkaz nemůže ovlivnit řádek podruhé
označuje, že se pokoušíte vložit stejný řádek více než jednou jedním příkazem. Jinými slovy:máte podvody na (name, url, email)
ve vašem VALUES
seznam. Složte duplikáty (pokud je to možné) a mělo by to fungovat. Ale budete se muset rozhodnout, který řádek z každé sady podvodů vyberete.
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;
Protože používáme volně stojící VALUES
výrazu, musíte přidat explicitní přetypování pro jiné než výchozí typy. Jako:
VALUES
(timestamptz '2016-03-12 02:47:56+01'
, timestamptz '2016-03-12 02:47:56+01'
, 'n3', 'u3', 'e3')
...
Vaše timestamptz
sloupce vyžadují explicitní přetypování, zatímco typy řetězců mohou pracovat s výchozím text
. (Stále můžete přenášet do varchar(n)
hned.)
Existují způsoby, jak určit, který řádek vybrat z každé sady podvodů:
- Vybrat první řádek v každé skupině GROUP BY?
Máte pravdu, není (momentálně) žádný způsob, jak se vyloučit řádků v RETURNING
doložka. Cituji Postgres Wiki:
Všimněte si, že
RETURNING
nezobrazuje "EXCLUDED.*
" alias zUPDATE
(pouze obecný "TARGET.*
" je tam viditelný alias). Předpokládá se, že tím vznikne obtěžující nejednoznačnost pro jednoduché, běžné případy [30] s malým nebo žádným přínosem. Někdy v budoucnu budeme možná hledat způsob, jak odhalit, pokudRETURNING
-projektované n-tice byly vloženy a aktualizovány, ale to pravděpodobně není nutné, aby to bylo v první potvrzené iteraci funkce [31].
Nicméně , neměli byste aktualizovat řádky, které by neměly být aktualizovány. Prázdné aktualizace jsou téměř stejně drahé jako běžné aktualizace – a mohou mít nezamýšlené vedlejší účinky. Pro začátek nutně nepotřebujete UPSERT, váš případ vypadá spíše jako "SELECT or INSERT". Související:
- Je SELECT nebo INSERT ve funkci náchylný k závodům?
Jedna čistší způsob vložení sady řádků by byl s CTE modifikujícími data:
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);
Přidaná složitost by měla platit za velké tabulky, kde INSERT
je pravidlo a SELECT
výjimka.
Původně jsem přidal NOT EXISTS
predikát na posledním SELECT
aby se předešlo duplicitám ve výsledku. Ale to bylo zbytečné. Všem CTE jednoho dotazu se zobrazují stejné snímky tabulek. Sada se vrátila s ON CONFLICT (name, url, email) DO NOTHING
se vzájemně vylučuje pro sadu vrácenou po INNER JOIN
ve stejných sloupcích.
Bohužel se tím také otevře malé okno pro podmínku závodu . Pokud ...
- souběžná transakce vloží konfliktní řádky
- zatím se nezavázal
- ale nakonec se zaváže
... některé řádky mohou být ztraceny.
Můžete jen INSERT .. ON CONFLICT DO NOTHING
, za kterým následuje samostatný SELECT
dotaz pro všechny řádky - v rámci stejné transakce k překonání tohoto. Což zase otevírá další malé okno pro podmínku závodu pokud mohou souběžné transakce potvrdit zápisy do tabulky mezi INSERT
a SELECT
(ve výchozím nastavení READ COMMITTED
úroveň izolace). Lze se mu vyhnout pomocí REPEATABLE READ
izolace transakcí (nebo přísnější). Nebo s (možná drahým nebo dokonce nepřijatelným) zámkem zápisu na celý stůl. Můžete získat jakékoli chování, které potřebujete, ale může za to zaplatit.
Související:
- Jak používat RETURNING s ON CONFLICT v PostgreSQL?
- Vraťte řádky z INSERT pomocí ON CONFLICT bez nutnosti aktualizace