Otrzymany błąd:
Polecenie ON CONFLICT DO UPDATE nie może wpłynąć na wiersz po raz drugi
wskazuje, że próbujesz przesunąć ten sam wiersz więcej niż raz w jednym poleceniu. Innymi słowy:masz duplikaty na (name, url, email)
w Twoich VALUES
lista. Złóż duplikaty (jeśli jest taka opcja) i powinno działać. Ale będziesz musiał zdecydować, który wiersz wybrać z każdego zestawu duplikatów.
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;
Ponieważ używamy wolnostojących VALUES
wyrażenie teraz musisz dodać jawne rzutowania typu dla typów innych niż domyślne. Na przykład:
VALUES
(timestamptz '2016-03-12 02:47:56+01'
, timestamptz '2016-03-12 02:47:56+01'
, 'n3', 'u3', 'e3')
...
Twój timestamptz
kolumny wymagają jawnego rzutowania typu, podczas gdy typy ciągów mogą działać z domyślnym text
. (Nadal możesz rzutować na varchar(n)
od razu.)
Istnieją sposoby na określenie, który wiersz wybrać z każdego zestawu duplikatów:
- Wybrać pierwszy wiersz w każdej grupie GROUP BY?
Masz rację, nie ma (obecnie) sposobu na wykluczenie wiersze w RETURNING
klauzula. Cytuję Postgres Wiki:
Zwróć uwagę, że RETURNING
nie uwidacznia „EXCLUDED.*
alias z UPDATE
(tylko ogólne „TARGET.*
" alias jest tam widoczny). Uważa się, że spowoduje to irytującą niejednoznaczność dla tych prostych, powszechnych przypadków [30] z niewielką lub żadną korzyścią. W pewnym momencie w przyszłości możemy poszukać sposobu na ujawnienie, jeśliRETURNING
-projected krotki zostały wstawione i zaktualizowane, ale prawdopodobnie nie musi to zrobić w pierwszej zatwierdzonej iteracji funkcji [31].
Jednak , nie należy aktualizować wierszy, które nie powinny być aktualizowane. Puste aktualizacje są prawie tak samo drogie jak zwykłe aktualizacje – i mogą mieć niezamierzone skutki uboczne. Na początku nie potrzebujesz koniecznie UPSERT, twoja sprawa wygląda bardziej jak „WYBIERZ lub WSTAW”. Powiązane:
- Czy SELECT lub INSERT w funkcji podatnej na wyścigi?
Jeden czystszy sposób wstawiania zestawu wierszy byłby z CTE modyfikującymi dane:
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);
Dodatkowa złożoność powinna opłacać się za duże tabele, w których INSERT
jest regułą i SELECT
wyjątek.
Pierwotnie dodałem NOT EXISTS
predykat na ostatnim SELECT
aby uniknąć duplikatów w wyniku. Ale to było zbędne. Wszystkie CTE pojedynczego zapytania wyświetlają te same migawki tabel. Zestaw zwrócony z ON CONFLICT (name, url, email) DO NOTHING
wyklucza się wzajemnie w zestawie zwróconym po INNER JOIN
na tych samych kolumnach.
Niestety otwiera to również małe okno na sytuację wyścigową . Jeśli...
- jednoczesna transakcja wstawia sprzeczne wiersze
- jeszcze nie popełnił zobowiązania
- ale ostatecznie zobowiązuje
... niektóre wiersze mogą zostać utracone.
Możesz po prostu INSERT .. ON CONFLICT DO NOTHING
, po którym następuje oddzielny SELECT
zapytanie dla wszystkich wierszy - w ramach tej samej transakcji, aby rozwiązać ten problem. Co z kolei otwiera kolejne małe okno na sytuację wyścigową czy współbieżne transakcje mogą zatwierdzać zapisy do tabeli między INSERT
i SELECT
(domyślnie READ COMMITTED
poziom izolacji). Można tego uniknąć za pomocą REPEATABLE READ
izolacja transakcji (lub bardziej rygorystyczna). Lub z (prawdopodobnie kosztowną lub nawet niedopuszczalną) blokadą zapisu na całym stole. Możesz uzyskać dowolne zachowanie, ale może to być cena do zapłacenia.
Powiązane:
- Jak używać RETURNING z ON CONFLICT w PostgreSQL?
- Zwróć wiersze z INSERT z ON CONFLICT bez konieczności aktualizacji