Ich habe zwei Spalten in der Tabelle col1
, col2
, beide sind eindeutig indiziert (col1 ist eindeutig und auch col2).
Ich muss beim Einfügen in diese Tabelle die ON CONFLICT
-Syntax verwenden und andere Spalten aktualisieren, aber ich kann nicht beide Spalten in conflict_target
clause verwenden.
Es klappt:
INSERT INTO table
...
ON CONFLICT ( col1 )
DO UPDATE
SET
-- update needed columns here
Aber wie man das für mehrere Kolonnen macht, etwa so:
...
ON CONFLICT ( col1, col2 )
DO UPDATE
SET
....
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');
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.
conflict_target kann eine eindeutige Indexinferenz durchführen. Beim Auftreten Zusammenfassend besteht sie aus einer oder mehreren Spalten index_column_name und/oder Ausdrucksausdrücke für index_expression und ein optionales index_predicate. Alles Tabellenname eindeutige Indizes, die unabhängig von der Reihenfolge .__ enthalten. Es werden genau die von Konfliktziel angegebenen Spalten/Ausdrücke abgeleitet (gewählt) als Arbiter-Indizes. Wenn ein index_predicate angegeben ist, ist es Als weitere Voraussetzung für die Inferenz müssen Arbiter-Indizes erfüllt werden.
Dies vermittelt den Eindruck, dass die folgende Abfrage funktionieren sollte. Dies ist jedoch nicht der Fall, da tatsächlich ein zusammen 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).
Postgresql verhält sich auf diese Weise, weil das, was passieren soll, wenn ein Konflikt in der zweiten Spalte auftritt, nicht genau definiert ist. Es gibt viele Möglichkeiten. Sollte in der obigen Q1-Abfrage beispielsweise postgresql col1
aktualisiert werden, wenn ein Konflikt mit col2
vorliegt? Was aber, wenn dies zu einem weiteren Konflikt bei col1
führt? Wie soll Postgresql damit umgehen?
Eine Lösung ist, 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üssen die Logik dieser gespeicherten Funktion so anpassen, dass die Spalten genau so aktualisiert werden, wie Sie es möchten. Rufe es gerne an
SELECT merge_db(3,2,'c');
SELECT merge_db(1,2,'d');
ON CONFLICT
erfordert einen eindeutigen Index * für die Konflikterkennung. Sie müssen also nur einen eindeutigen Index für beide Spalten erstellen:
t=# create table t (id integer, a text, b text);
CREATE TABLE
t=# create unique index idx_t_id_a on t (id, a);
CREATE INDEX
t=# insert into t values (1, 'a', 'foo');
INSERT 0 1
t=# insert into t values (1, 'a', 'bar') on conflict (id, a) do update set b = 'bar';
INSERT 0 1
t=# select * from t;
id | a | b
----+---+-----
1 | a | bar
* Neben eindeutigen Indizes können Sie auch Ausschlussbedingungen verwenden. Dies sind etwas allgemeiner als eindeutige Einschränkungen. Angenommen, Ihre Tabelle hatte Spalten für id
und valid_time
(und valid_time
ist eine tsrange
), und Sie wollten doppelte id
s zulassen, nicht jedoch für sich überschneidende Zeiträume. Eine eindeutige Einschränkung ist nicht hilfreich, aber mit einer Ausschlussbeschränkung können Sie sagen: "Neue Datensätze ausschließen, wenn ihre id
einer alten id
entspricht und deren valid_time
ihren valid_time
überlappt."
ODER UND
Vlad hatte die richtige Idee.
Zuerst müssen Sie eine tabellenspezifische Einschränkung für die Spalten col1, col2
erstellen. Danach können Sie Folgendes tun:
INSERT INTO dupes values(3,2,'c')
ON CONFLICT ON CONSTRAINT dupes_pkey
DO UPDATE SET col3 = 'c', col2 = 2
Wenn Sie Postgres 9.5 verwenden, können Sie den EXCLUDED-Bereich verwenden.
Beispiel aus Was ist neu in PostgreSQL 9.5 :
INSERT INTO user_logins (username, logins)
VALUES ('Naomi',1),('James',1)
ON CONFLICT (username)
DO UPDATE SET logins = user_logins.logins + EXCLUDED.logins;
Irgendwie hackig, aber ich habe dieses Problem gelöst, indem ich die beiden Werte von col1 und col2 in einer neuen Spalte, col3 (Art wie ein Index der beiden) verkettete, und es mit dem Vergleich verglich. Dies funktioniert nur, wenn Sie BEIDE col1 und col2 benötigen.
INSERT INTO table
...
ON CONFLICT ( col3 )
DO UPDATE
SET
-- update needed columns here
Dabei ist col3 die Verkettung der Werte aus col1 und col2.
Sie können normalerweise (ich würde denken) eine Anweisung mit nur einem on conflict
generieren, der die einzige Einschränkung angibt, die für das von Ihnen eingefügte Objekt relevant ist.
Normalerweise ist immer nur eine Einschränkung die "relevante" Einschränkung. (Wenn viele, dann frage ich mich, ob etwas komisch/seltsam gestaltet ist, hmm.)
Beispiel:
(Lizenz: Nicht CC0, nur CC-By)
// there're these unique constraints:
// unique (site_id, people_id, page_id)
// unique (site_id, people_id, pages_in_whole_site)
// unique (site_id, people_id, pages_in_category_id)
// and only *one* of page-id, category-id, whole-site-true/false
// can be specified. So only one constraint is "active", at a time.
val thingColumnName = thingColumnName(notfificationPreference)
val insertStatement = s"""
insert into page_notf_prefs (
site_id,
people_id,
notf_level,
page_id,
pages_in_whole_site,
pages_in_category_id)
values (?, ?, ?, ?, ?, ?)
-- There can be only one on-conflict clause.
on conflict (site_id, people_id, $thingColumnName) <—— look
do update set
notf_level = excluded.notf_level
"""
val values = List(
siteId.asAnyRef,
notfPref.peopleId.asAnyRef,
notfPref.notfLevel.toInt.asAnyRef,
// Only one of these is non-null:
notfPref.pageId.orNullVarchar,
if (notfPref.wholeSite) true.asAnyRef else NullBoolean,
notfPref.pagesInCategoryId.orNullInt)
runUpdateSingleRow(insertStatement, values)
Und:
private def thingColumnName(notfPref: PageNotfPref): String =
if (notfPref.pageId.isDefined)
"page_id"
else if (notfPref.pagesInCategoryId.isDefined)
"pages_in_category_id"
else if (notfPref.wholeSite)
"pages_in_whole_site"
else
die("TyE2ABK057")
Die on conflict
-Klausel wird abhängig von dem, was ich versuche, dynamisch generiert. Wenn ich eine Benachrichtigungseinstellung für eine Seite einsetze, kann es einen eindeutigen Konflikt in der site_id, people_id, page_id
-Einschränkung geben. Und wenn ich Benachrichtigungspräferenzen für eine Kategorie konfiguriere, weiß ich stattdessen, dass die Einschränkung, die verletzt werden kann, site_id, people_id, category_id
ist.
Ich kann also, und wahrscheinlich auch Sie, in Ihrem Fall? Die korrekte on conflict (... columns )
generieren, weil ich weiß, was ich will tun soll, und dann weiß ich, welche der vielen einzigartigen Einschränkungen die einzige ist kann verletzt werden.
ON CONFLICT ist eine sehr ungeschickte Lösung
UPDATE dupes SET key1=$1, key2=$2 where key3=$3
if rowcount > 0
INSERT dupes (key1, key2, key3) values ($1,$2,$3);
funktioniert mit Oracle, Postgres und allen anderen Datenbanken