798 Stimmen

Einfügen, bei Duplikaten aktualisieren in PostgreSQL?

Vor mehreren Monaten habe ich aus einer Antwort auf Stack Overflow gelernt, wie man mehrere Updates gleichzeitig in MySQL mit der folgenden Syntax durchführen kann:

INSERT INTO Tabelle (id, Feld, Feld2) VALUES (1, A, X), (2, B, Y), (3, C, Z)
ON DUPLICATE KEY UPDATE field=VALUES(Col1), field2=VALUES(Col2);

Jetzt bin ich zu PostgreSQL gewechselt und anscheinend ist das nicht korrekt. Es verweist auf alle richtigen Tabellen, daher nehme ich an, dass es nur darum geht, dass unterschiedliche Schlüsselwörter verwendet werden, aber ich bin mir nicht sicher, wo dies in der PostgreSQL-Dokumentation behandelt wird.

Zur Klarstellung: Ich möchte mehrere Dinge einfügen und wenn sie bereits vorhanden sind, sie aktualisieren.

46 Stimmen

Jeder, der diese Frage findet, sollte den Artikel von Depesz "Warum ist upsert so kompliziert?" lesen. Es erklärt das Problem und mögliche Lösungen sehr gut.

9 Stimmen

UPSERT wird in Postgres 9.5 hinzugefügt: wiki.postgresql.org/wiki/…

6 Stimmen

@tommed - es wurde erledigt: stackoverflow.com/a/34639631/4418

14voto

Es gibt keinen einfachen Befehl, um dies zu tun.

Der korrekteste Ansatz ist die Verwendung einer Funktion, wie z.B. die aus Docs.

Eine andere Lösung (obwohl nicht so sicher) ist ein Update mit Rückgabe zu machen, um zu überprüfen, welche Zeilen aktualisiert wurden, und den Rest einzufügen.

So etwas in der Art:

update table
set column = x.column
from (values (1,'aa'),(2,'bb'),(3,'cc')) as x (id, column)
where table.id = x.id
returning id;

Angenommen, die id:2 wurde zurückgegeben:

insert into table (id, column) values (1, 'aa'), (3, 'cc');

Natürlich wird es früher oder später scheitern (in einer gleichzeitigen Umgebung), da hier ein klarer Wettlauf besteht, aber normalerweise funktioniert es.

Hier ist ein längerer und umfassenderer Artikel zum Thema.

1 Stimmen

Wenn Sie diese Option verwenden, stellen Sie sicher, dass die ID auch dann zurückgegeben wird, wenn das Update nichts bewirkt. Ich habe Datenbanken gesehen, die Abfragen wie "Update table foo set bar = 4 where bar = 4" optimieren.

11voto

Mise Punkte 2787

Ich verwende diese Funktion merge

ERSTELLEN ODER ERSETZEN FUNKTION merge_tabla(key INT, data TEXT)
  GIBT void ALS
$BODY$
BEGIN
    WENN VORHANDEN (SELECT a FROM tabla WHERE a = key)
        DANN
            UPDATE tabla SET b = data WHERE a = key;
        RETURN;
    SONST
        INSERT INTO tabla(a,b) VALUES (key, data);
        RETURN;
    END IF;
END;
$BODY$
SPRACHE plpgsql

2 Stimmen

Es ist effizienter, einfach zuerst das Update durchzuführen und dann die Anzahl der aktualisierten Zeilen zu überprüfen. (Siehe Antwort von Ahmad)

10voto

Ch'marr Punkte 1224

Persönlich habe ich eine "Regel" mit dem insert-Befehl verknüpft. Angenommen, Sie hatten eine "dns"-Tabelle, die dns-Treffer pro Kunde basierend auf einer bestimmten Zeitbasis aufzeichnet:

CREATE TABLE dns (
    "time" timestamp without time zone NOT NULL,
    customer_id integer NOT NULL,
    hits integer
);

Sie möchten in der Lage sein, Zeilen mit aktualisierten Werten neu einzufügen oder sie zu erstellen, wenn sie noch nicht vorhanden sind. Dabei wird der customer_id und die Zeit als Schlüssel verwendet. Etwas in dieser Art:

CREATE RULE replace_dns AS 
    ON INSERT TO dns 
    WHERE (EXISTS (SELECT 1 FROM dns WHERE ((dns."time" = new."time") 
            AND (dns.customer_id = new.customer_id)))) 
    DO INSTEAD UPDATE dns 
        SET hits = new.hits 
        WHERE ((dns."time" = new."time") AND (dns.customer_id = new.customer_id));

Update: Dies hat das Potenzial, zu scheitern, wenn gleichzeitige Einfügungen stattfinden, da es zu unique_violation Ausnahmen kommen kann. Die nicht abgeschlossene Transaktion wird jedoch fortgesetzt und erfolgreich abgeschlossen, und Sie müssen die abgebrochene Transaktion einfach wiederholen.

Jedoch, wenn ständig viele Einfügungen stattfinden, möchten Sie vielleicht eine Tabellensperre um die insert-Befehle herum setzen: SHARE ROW EXCLUSIVE-Sperren verhindern alle Operationen, die Zeilen in Ihrer Ziel-Tabelle einfügen, löschen oder aktualisieren könnten. Updates, die den eindeutigen Schlüssel nicht aktualisieren, sind jedoch sicher, also wenn keine Operation dies tun wird, verwenden Sie stattdessen Bezeichnungssperren.

Außerdem verwendet der COPY-Befehl keine REGELN, daher müssen Sie bei Einfügungen mit COPY statt dessen Trigger verwenden.

8voto

alexkovelsky Punkte 3305

Ähnlich wie die beliebteste Antwort, aber etwas schneller funktioniert:

WITH upsert AS (UPDATE spider_count SET tally=1 WHERE date='today' RETURNING *)
INSERT INTO spider_count (spider, tally) SELECT 'Googlebot', 1 WHERE NOT EXISTS (SELECT * FROM upsert)

(Quelle: http://www.the-art-of-web.com/sql/upsert/)

5 Stimmen

Das wird scheitern, wenn es gleichzeitig in zwei Sitzungen ausgeführt wird, da keine Aktualisierung eine vorhandene Zeile sehen wird. Beide Aktualisierungen treffen also keine Zeilen, und beide Abfragen werden ein INSERT ausführen.

7voto

Felipe FMMobile Punkte 1631

Ich habe die "upsert" Funktion oben angepasst, wenn du INSERT AND REPLACE möchtest:

`

 CREATE OR REPLACE FUNCTION upsert(sql_insert text, sql_update text)

 RETURNS void AS
 $BODY$
 BEGIN
    -- Versuche zuerst einzufügen und dann zu aktualisieren. Hinweis: Einfügen hat ein Primärschlüssel und Aktualisieren nicht...

    EXECUTE sql_insert;
    RETURN;
    EXCEPTION WHEN unique_violation THEN
    EXECUTE sql_update; 
    IF FOUND THEN 
        RETURN; 
    END IF;
 END;
 $BODY$
 LANGUAGE plpgsql VOLATILE
 COST 100;
 ALTER FUNCTION upsert(text, text)
 OWNER TO postgres;

Und nach der Ausführung, mache etwas Ähnliches wie dies:

SELECT upsert($$INSERT INTO ...$$,$$UPDATE... $$)

Es ist wichtig, doppelte Dollar-Kommas zu verwenden, um Compilerfehler zu vermeiden

  • Überprüfe die Geschwindigkeit...

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X