Der Fehler, den Sie erhalten:
Der Befehl ON CONFLICT DO UPDATE kann die Zeile nicht ein zweites Mal beeinflussen
gibt an, dass Sie versuchen, dieselbe Zeile mehr als einmal in einem einzigen Befehl zu aktualisieren. Mit anderen Worten:Sie haben Duplikate auf (name, url, email)
in Ihren VALUES
aufführen. Duplikate falten (falls das eine Option ist) und es sollte funktionieren. Aber Sie müssen entscheiden, welche Reihe Sie aus jedem Satz von Duplikaten auswählen möchten.
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 wir einen freistehenden VALUES
verwenden -Ausdruck jetzt müssen Sie explizite Typumwandlungen für nicht standardmäßige Typen hinzufügen. Wie:
VALUES
(timestamptz '2016-03-12 02:47:56+01'
, timestamptz '2016-03-12 02:47:56+01'
, 'n3', 'u3', 'e3')
...
Ihr timestamptz
Spalten benötigen eine explizite Typumwandlung, während die String-Typen mit dem Standardwert text
arbeiten können . (Sie könnten immer noch in varchar(n)
umwandeln sofort.)
Es gibt Möglichkeiten, um zu bestimmen, welche Zeile aus jedem Satz von Duplikaten ausgewählt werden soll:
- Erste Zeile in jeder GROUP BY-Gruppe auswählen?
Sie haben Recht, es gibt (derzeit) keine Möglichkeit, ausgeschlossen zu werden Zeilen in RETURNING
Klausel. Ich zitiere das Postgres-Wiki:
Beachten Sie, dass RETURNING
macht das "EXCLUDED.*
" nicht sichtbar " Alias aus dem UPDATE
(nur das generische "TARGET.*
" Alias ist dort sichtbar). Es wird angenommen, dass dies zu lästigen Mehrdeutigkeiten für die einfachen, häufigen Fälle [30] mit wenig bis gar keinem Nutzen führt. Irgendwann in der Zukunft werden wir vielleicht einen Weg verfolgen, um aufzudecken, obRETURNING
-projizierte Tupel wurden eingefügt und aktualisiert, aber das muss es wahrscheinlich nicht in die erste festgeschriebene Iteration des Features [31] schaffen.
Allerdings , sollten Sie keine Zeilen aktualisieren, die nicht aktualisiert werden sollen. Leere Updates sind fast so teuer wie normale Updates – und können unbeabsichtigte Nebeneffekte haben. Sie brauchen UPSERT nicht unbedingt, Ihr Fall sieht eher aus wie "SELECT or INSERT". Verwandte:
- Ist SELECT oder INSERT in einer Funktion anfällig für Race-Conditions?
Einer Eine sauberere Methode zum Einfügen einer Reihe von Zeilen wäre die Verwendung von datenmodifizierenden CTEs:
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);
Die zusätzliche Komplexität sollte sich für große Tabellen auszahlen, in denen INSERT
ist die Regel und SELECT
die Ausnahme.
Ursprünglich hatte ich einen NOT EXISTS
hinzugefügt Prädikat auf dem letzten SELECT
um Duplikate im Ergebnis zu vermeiden. Aber das war überflüssig. Alle CTEs einer einzelnen Abfrage sehen dieselben Snapshots von Tabellen. Das mit ON CONFLICT (name, url, email) DO NOTHING
zurückgegebene Set schließen sich gegenseitig mit dem Satz aus, der nach dem INNER JOIN
zurückgegeben wird in denselben Spalten.
Leider öffnet dies auch ein winziges Fenster für eine Rennbedingung . Wenn ...
- Eine gleichzeitige Transaktion fügt widersprüchliche Zeilen ein
- hat noch keine Zusage gemacht
- aber übergibt schließlich
... einige Zeilen können verloren gehen.
Sie könnten einfach INSERT .. ON CONFLICT DO NOTHING
, gefolgt von einem separaten SELECT
Abfrage für alle Zeilen - innerhalb derselben Transaktion, um dies zu überwinden. Was wiederum ein weiteres winziges Fenster für eine Rennbedingung öffnet wenn gleichzeitige Transaktionen Schreibvorgänge in die Tabelle zwischen INSERT
festschreiben können und SELECT
(standardmäßig READ COMMITTED
Isolationsstufe). Kann mit REPEATABLE READ
vermieden werden Transaktionsisolation (oder strenger). Oder mit einer (möglicherweise teuren oder sogar inakzeptablen) Schreibsperre auf der gesamten Tabelle. Sie können jedes Verhalten bekommen, das Sie brauchen, aber es kann sein, dass Sie dafür einen Preis zahlen müssen.
Verwandte:
- Wie verwendet man RETURNING mit ON CONFLICT in PostgreSQL?
- Zeilen von INSERT mit ON CONFLICT zurückgeben, ohne aktualisieren zu müssen