Eine Beispieltabelle und Daten
CREATE TABLE dupes(col1 int primary key, col2 int, col3 text,
CONSTRAINT col2_unique UNIQUE (col2)
);
INSERT INTO dupes values(1,1,'a'),(2,2,'b');
Das Problem reproduzieren
INSERT INTO dupes values(3,2,'c')
ON CONFLICT (col1) DO UPDATE SET col3 = 'c', col2 = 2
Nennen wir das Q1. Das Ergebnis ist
ERROR: duplicate key value violates unique constraint "col2_unique"
DETAIL: Key (col2)=(2) already exists.
konfliktziel kann eine eindeutige Indexinferenz durchführen. Wenn eine Inferenz durchgeführt wird, besteht sie aus einer oder mehreren Spalten mit dem Indexspaltennamen und / oder den Ausdrücken mit dem Indexausdruck sowie einem optionalen Indexpredikat. Alle eindeutigen Indizes für Tabellennamen, die unabhängig von der Reihenfolge genau die von Konfliktzielen angegebenen Spalten / Ausdrücke enthalten, werden als Arbiter-Indizes abgeleitet (ausgewählt). Wenn ein index_predicate angegeben wird, muss es als weitere Voraussetzung für die Inferenz die Arbiter-Indizes erfüllen.
Dies erweckt den Eindruck, dass die folgende Abfrage funktionieren sollte, jedoch nicht, da tatsächlich ein eindeutiger Index für col1 und col2 erforderlich wäre. Ein solcher Index würde jedoch nicht garantieren, dass col1 und col2 einzeln eindeutig sind, was eine der Anforderungen des OP ist.
INSERT INTO dupes values(3,2,'c')
ON CONFLICT (col1,col2) DO UPDATE SET col3 = 'c', col2 = 2
Nennen wir diese Abfrage Q2 (dies schlägt mit einem Syntaxfehler fehl).
Warum?
Postgresql verhält sich so, weil nicht genau definiert ist, was passieren soll, wenn ein Konflikt in der zweiten Spalte auftritt. Es gibt verschiedene Möglichkeiten. Sollte postgresql beispielsweise in der obigen Q1-Abfrage aktualisiert werden, col1
wenn ein Konflikt besteht col2
? Aber was ist, wenn das zu einem weiteren Konflikt führt col1
? Wie soll postgresql damit umgehen?
Eine Lösung
Eine Lösung besteht darin, ON CONFLICT mit altmodischem UPSERT zu kombinieren .
CREATE OR REPLACE FUNCTION merge_db(key1 INT, key2 INT, data TEXT) RETURNS VOID AS
$$
BEGIN
LOOP
-- first try to update the key
UPDATE dupes SET col3 = data WHERE col1 = key1 and col2 = key2;
IF found THEN
RETURN;
END IF;
-- not there, so try to insert the key
-- if someone else inserts the same key concurrently, or key2
-- already exists in col2,
-- we could get a unique-key failure
BEGIN
INSERT INTO dupes VALUES (key1, key2, data) ON CONFLICT (col1) DO UPDATE SET col3 = data;
RETURN;
EXCEPTION WHEN unique_violation THEN
BEGIN
INSERT INTO dupes VALUES (key1, key2, data) ON CONFLICT (col2) DO UPDATE SET col3 = data;
RETURN;
EXCEPTION WHEN unique_violation THEN
-- Do nothing, and loop to try the UPDATE again.
END;
END;
END LOOP;
END;
$$
LANGUAGE plpgsql;
Sie müssten die Logik dieser gespeicherten Funktion so ändern, dass die Spalten genau so aktualisiert werden, wie Sie es möchten. Rufe es wie auf
SELECT merge_db(3,2,'c');
SELECT merge_db(1,2,'d');