Грешката, която получавате:
ON CONFLICT DO UPDATE командата не може да повлияе на реда втори път
показва, че се опитвате да вдигнете един и същ ред повече от веднъж в една команда. С други думи:имате измамници на (name, url, email)
във вашите VALUES
списък. Сгънете дубликатите (ако това е опция) и трябва да работи. Но ще трябва да решите кой ред да изберете от всеки набор от измамници.
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;
Тъй като използваме самостоятелен VALUES
израз сега, трябва да добавите изрично привеждане на типове за типове, които не са по подразбиране. Като:
VALUES
(timestamptz '2016-03-12 02:47:56+01'
, timestamptz '2016-03-12 02:47:56+01'
, 'n3', 'u3', 'e3')
...
Вашият timestamptz
колоните се нуждаят от изрично прехвърляне на тип, докато типовете низове могат да работят с text
по подразбиране . (Все още можете да прехвърляте към varchar(n)
веднага.)
Има начини да определите кой ред да изберете от всеки набор от измамници:
- Изберете ли първия ред във всяка група GROUP BY?
Прав сте, няма (в момента) начин да се изключите редове в RETURNING
клауза. Цитирам Postgres Wiki:
Обърнете внимание, че
RETURNING
не прави видимо „EXCLUDED.*
" псевдоним отUPDATE
(само общият "TARGET.*
" псевдонимът е видим там). Смята се, че това създава досадна двусмислие за прости, често срещани случаи [30] с малка или никаква полза. В някакъв момент в бъдещето може да търсим начин да разкрием ifRETURNING
-проектираните кортежи бяха вмъкнати и актуализирани, но това вероятно не е необходимо да го включва в първата ангажирана итерация на функцията [31].
Въпреки това , не трябва да актуализирате редове, които не трябва да бъдат актуализирани. Празните актуализации са почти толкова скъпи, колкото обикновените актуализации - и може да имат нежелани странични ефекти. Не се нуждаете строго от UPSERT за начало, вашият случай изглежда по-скоро като „SELECT или INSERT“. Свързано:
- Изберете или INSERT във функция предразположени ли са условия на състезание?
Едно по-чист начин за вмъкване на набор от редове би бил с CTE, модифициращи данни:
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);
Добавената сложност би трябвало да плати за големи таблици, където INSERT
е правилото и SELECT
изключението.
Първоначално бях добавил NOT EXISTS
предикат на последния SELECT
за да предотвратите дублиране в резултата. Но това беше излишно. Всички CTE на една заявка виждат едни и същи моментни снимки на таблици. Наборът се върна с ON CONFLICT (name, url, email) DO NOTHING
се изключва взаимно за набора, върнат след INNER JOIN
на същите колони.
За съжаление това също отваря малък прозорец за условия на състезание . Ако...
- едновременна транзакция вмъква конфликтни редове
- все още не е поел ангажимент
- но в крайна сметка се ангажира
... някои редове може да бъдат загубени.
Може просто да INSERT .. ON CONFLICT DO NOTHING
, последвано от отделен SELECT
заявка за всички редове - в рамките на една и съща транзакция, за да се преодолее това. Което от своя страна отваря другмалък прозорец за състояние на състезание ако едновременните транзакции могат да извършват записи в таблицата между INSERT
и SELECT
(по подразбиране READ COMMITTED
ниво на изолация). Може да се избегне с REPEATABLE READ
изолация на транзакциите (или по-строга). Или с (вероятно скъпо или дори неприемливо) заключване на запис на цялата маса. Можете да получите всяко поведение, от което се нуждаете, но може да има цена, която трябва да платите.
Свързано:
- Как да използвам RETURNING с ON CONFLICT в PostgreSQL?
- Връщане на редове от INSERT с ON CONFLICT без необходимост от актуализиране