Frage

Vor einigen Monaten habe ich von einer Antwort auf Stack-Überlauf, wie auf einmal in MySQL mit folgenden Syntax mehr Aktualisierungen auszuführen:

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

Ich habe eingeschaltet jetzt PostgreSQL über und anscheinend ist das nicht richtig. Es wird Bezug auf alle die richtigen Tabellen, damit ich es ist eine Frage der verschiedenen Keywords übernehmen verwendet werden, aber ich bin nicht sicher, wo in der PostgreSQL-Dokumentation dieses abgedeckt ist.

Um klären, ich mehrere Dinge einfügen wollen, und wenn sie bereits vorhanden sind, sie zu aktualisieren.

War es hilfreich?

Lösung

PostgreSQL ab Version 9.5 hat UPSERT Syntax, mit < strong> KONFLIKT Klausel. mit der folgenden Syntax (ähnlich wie MySQL)

INSERT INTO the_table (id, column_1, column_2) 
VALUES (1, 'A', 'X'), (2, 'B', 'Y'), (3, 'C', 'Z')
ON CONFLICT (id) DO UPDATE 
  SET column_1 = excluded.column_1, 
      column_2 = excluded.column_2;

Suche postgresql E-Mail des Gruppenarchive für "Upsert" führt zu finden, ein Beispiel für das, was Sie vielleicht tun wollen, im Handbuch :

  

Beispiel 38-2. Ausnahmen mit UPDATE / INSERT

     

In diesem Beispiel wird die Ausnahmebehandlung entweder UPDATE oder INSERT, gegebenenfalls auszuführen:

CREATE TABLE db (a INT PRIMARY KEY, b TEXT);

CREATE FUNCTION merge_db(key INT, data TEXT) RETURNS VOID AS
$$
BEGIN
    LOOP
        -- first try to update the key
        -- note that "a" must be unique
        UPDATE db SET b = data WHERE a = key;
        IF found THEN
            RETURN;
        END IF;
        -- not there, so try to insert the key
        -- if someone else inserts the same key concurrently,
        -- we could get a unique-key failure
        BEGIN
            INSERT INTO db(a,b) VALUES (key, data);
            RETURN;
        EXCEPTION WHEN unique_violation THEN
            -- do nothing, and loop to try the UPDATE again
        END;
    END LOOP;
END;
$$
LANGUAGE plpgsql;

SELECT merge_db(1, 'david');
SELECT merge_db(1, 'dennis');

Es ist vielleicht ein Beispiel dafür, wie diese in der Masse zu tun, mit CTEs in 9.1 und höher, in der Hacker-Mailingliste :

WITH foos AS (SELECT (UNNEST(%foo[])).*)
updated as (UPDATE foo SET foo.a = foos.a ... RETURNING foo.id)
INSERT INTO foo SELECT foos.* FROM foos LEFT JOIN updated USING(id)
WHERE updated.id IS NULL;

Siehe a_horse_with_no_name Antwort für ein klareres Beispiel.

Andere Tipps

. Achtung: dies ist nicht sicher aus mehreren Sitzungen zur gleichen Zeit ausgeführt, wenn (siehe Einsprüche unten)


Eine weitere clevere Art und Weise eine „UPSERT“ in postgresql zu tun ist, zwei aufeinanderfolgende UPDATE / INSERT-Anweisungen zu tun, die jeweils ausgelegt sind, um erfolgreich zu sein oder haben keine Wirkung.

UPDATE table SET field='C', field2='Z' WHERE id=3;
INSERT INTO table (id, field, field2)
       SELECT 3, 'C', 'Z'
       WHERE NOT EXISTS (SELECT 1 FROM table WHERE id=3);

Das Update wird erfolgreich sein, wenn eine Zeile mit „id = 3“ ist bereits vorhanden, sonst hat es keine Wirkung.

Die INSERT wird nur gelingen, wenn Zeile mit "id = 3" existiert nicht bereits.

Sie können diese beiden in einem einzigen String kombinieren und sie beide mit einer einzigen SQL-Anweisung ausführen, aus Ihrer Anwendung ausgeführt werden. sie zusammen in einer einzigen Transaktion ausgeführt wird, sehr zu empfehlen.

Das funktioniert sehr gut, wenn es allein oder auf einem gesperrten Tisch laufen, sondern unterliegt Bedingungen fahren, dass es nach wie vor mit doppelten Schlüssel Fehler fehlschlagen könnte bedeuten, wenn eine Zeile gleichzeitig eingeführt wird, oder könnte ohne Zeile beenden eingefügt, wenn eine Zeile wird gleichzeitig gelöscht. Eine SERIALIZABLE Transaktion auf PostgreSQL 9.1 oder höher wird es auf Kosten eines sehr hohen Serialisierung Ausfallrate, das heißt, Sie werden viel Griff zuverlässig wiederholen haben. Siehe warum UPSERT so kompliziert , die diskutiert diesen Fall näher.

Dieser Ansatz wird auch unterliegt verloren Updates in read committed Isolation, wenn die Anwendung die betroffene Zeilenanzahl überprüft und überprüft, ob entweder die insert oder die update eine Reihe betroffen.

Mit PostgreSQL 9.1 dies erreicht werden kann eine beschreibbare CTE mit ( allgemeiner Tabellenausdruck ) :

WITH new_values (id, field1, field2) as (
  values 
     (1, 'A', 'X'),
     (2, 'B', 'Y'),
     (3, 'C', 'Z')

),
upsert as
( 
    update mytable m 
        set field1 = nv.field1,
            field2 = nv.field2
    FROM new_values nv
    WHERE m.id = nv.id
    RETURNING m.*
)
INSERT INTO mytable (id, field1, field2)
SELECT id, field1, field2
FROM new_values
WHERE NOT EXISTS (SELECT 1 
                  FROM upsert up 
                  WHERE up.id = new_values.id)

Sehen Sie diese Blog-Einträge:


Beachten Sie, dass diese Lösung funktioniert nicht eine eindeutige Schlüssel Verletzung verhindern, aber es ist nicht anfällig für verloren Updates.
Sehen Sie sich die Follow-up von Craig Ringer auf dba.stackexchange.com

In PostgreSQL 9.5 und neuer können Sie INSERT ... ON CONFLICT UPDATE verwenden.

Siehe der Dokumentation .

Ein MySQL INSERT ... ON DUPLICATE KEY UPDATE kann direkt an einem ON CONFLICT UPDATE umformuliert werden. Weder ist SQL-Standardsyntax, sie beide Datenbank-spezifische Erweiterungen sind. Es gibt gute Gründe MERGE nicht für diese verwendet wurde, eine neue Syntax wurde nicht nur zum Spaß erstellt. (MySQL-Syntax hat auch Probleme, die es bedeutet nicht direkt übernommen).

z. gegeben Setup:

CREATE TABLE tablename (a integer primary key, b integer, c integer);
INSERT INTO tablename (a, b, c) values (1, 2, 3);

die MySQL-Abfrage:

INSERT INTO tablename (a,b,c) VALUES (1,2,3)
  ON DUPLICATE KEY UPDATE c=c+1;

wird:

INSERT INTO tablename (a, b, c) values (1, 2, 10)
ON CONFLICT (a) DO UPDATE SET c = tablename.c + 1;

Unterschiede:

  • Sie muss geben Sie den Spaltennamen (oder einen eindeutigen Constraint-Name) für die Einzigartigkeit Prüfung zu verwenden. Das ist die ON CONFLICT (columnname) DO

  • Das Schlüsselwort SET verwendet werden muss, als ob dies eine normale UPDATE Aussage war

Es hat einige nette Features auch:

  • Sie können eine WHERE Klausel auf Ihrem UPDATE haben (so dass Sie effektiv ON CONFLICT UPDATE in ON CONFLICT IGNORE für bestimmte Werte drehen)

  • Die vorgeschlagene-for-Einfügungswerte als Zeilenvariable EXCLUDED verfügbar sind, die die gleiche Struktur wie die Zieltabelle aufweist. Sie können die ursprünglichen Werte in der Tabelle erhalten, indem Sie die Tabellennamen verwenden. Also in diesem Fall EXCLUDED.c wird 10 (denn das ist, was wir einzufügen versuchen) und "table".c wird 3 werden, da, dass der aktuelle Wert in der Tabelle ist. Sie können entweder eine oder beide in der SET Ausdrücke und WHERE-Klausel verwenden.

Für Hintergrund auf Upsert finden Sie unter Wie UPSERT (merge, INSERT ... ON UPDATE DUPLICATE) in PostgreSQL?

Ich war für die gleiche Sache suchen, als ich hierher kam, aber das Fehlen einer allgemeinen „Upsert“ -Funktion botherd mich ein bisschen so dachte ich, Sie nur das Update passieren könnte und legen Sie SQL als Argumente dieser Funktion das Handbuch bilden

das würde wie folgt aussehen:

CREATE FUNCTION upsert (sql_update TEXT, sql_insert TEXT)
    RETURNS VOID
    LANGUAGE plpgsql
AS $$
BEGIN
    LOOP
        -- first try to update
        EXECUTE sql_update;
        -- check if the row is found
        IF FOUND THEN
            RETURN;
        END IF;
        -- not found so insert the row
        BEGIN
            EXECUTE sql_insert;
            RETURN;
            EXCEPTION WHEN unique_violation THEN
                -- do nothing and loop
        END;
    END LOOP;
END;
$$;

und vielleicht zu tun, was Sie zunächst tun wollten, batch „Upsert“ Sie Tcl verwenden könnten den SQL_UPDATE und Schleife des einzelnen Updates zu spalten, wird der preformance Hit sehr klein siehe http://archives.postgresql.org/pgsql-performance/2006-04/msg00557.php

die höchst Kosten die Abfrage von Code ausgeführt wird, auf der Seite Datenbank der Ausführungskosten viel kleiner

Es gibt keinen einfachen Befehl, es zu tun.

Der richtige Ansatz ist, Funktion zu nutzen, wie die von docs .

Eine andere Lösung (obwohl das nicht sicher) ist Update zu tun mit der Rückkehr zu überprüfen, welche Zeilen Updates waren, und fügen Sie den Rest von ihnen

Etwas entlang der Linien von:

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;

Annahme-ID: 2 wurde zurückgegeben:

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

Natürlich wird es früher oder später aus der Patsche helfen (in Umgebung mit gemeinsamer Zugriff), da Bedingung klar Rennen hier ist, aber in der Regel wird es funktionieren.

Hier ist ein länger und umfassender Artikel über die Thema .

Ich persönlich habe eine „Regel“ an die Insert-Anweisung eingerichtet. Sagen Sie bitte einen „dns“ Tisch haben die dns Treffer pro Kunden auf einer Basis pro Zeit aufgezeichnet:

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

Sie wollten in der Lage sein, sie wieder einzusetzen Reihen mit aktualisierten Werten, oder erstellen, wenn sie schon nicht existieren. Verkeilt auf der customer_id und der Zeit. So etwas wie folgt aus:

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 zum Scheitern verurteilt, wenn die gleichzeitigen Einsätze geschehen, da es unique_violation Ausnahmen zu generieren. Allerdings ist die nicht-terminierten Transaktion wird fortgesetzt und erfolgreich zu sein, und Sie müssen nur die abgebrochene Transaktion wiederholen.

Allerdings, wenn es Tonnen von Einsätzen sind die ganze Zeit geschieht, werden Sie rund um die Insert-Anweisungen, eine Tabellensperre setzen: SHARE ROW wird EXCLUSIVE Verriegelungs alle Vorgänge verhindern, die einfügen könnte, löschen oder zu aktualisieren Zeilen in der Zieltabelle. Allerdings Updates, die sicher nicht den eindeutigen Schlüssel aktualisieren, wenn Sie also keine Operation dies tun werden, statt beratende Sperren verwenden.

Auch dann, wenn der COPY-Befehl nicht Regeln verwenden, wenn Sie also mit COPY sind Einfügen, müssen Sie Trigger stattdessen verwenden.

I custom "Upsert" -Funktion oben, wenn Sie wollen INSERT UND ERSETZEN:

`

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

 RETURNS void AS
 $BODY$
 BEGIN
    -- first try to insert and after to update. Note : insert has pk and update not...

    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 auszuführen, etwas zu tun, wie folgt aus:

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

Wichtig ist doppelt Dollar-Komma setzen Compiler-Fehler zu vermeiden

  • Prüfen Sie die Geschwindigkeit ...

Wie bei den meisten gefiel Antwort, funktioniert aber etwas schneller:

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/ )

Ich habe das gleiche Problem für die Verwaltung von Kontoeinstellungen als Name-Wert-Paare. Die Design-Kriterien ist, dass verschiedene Kunden unterschiedliche Einstellungen Sätze haben könnten.

Meine Lösung, ähnlich wie JWP ist Bulk-Lösch- und ersetzen, die Zusammenführung Datensatz in Ihrer Anwendung zu erzeugen.

Das ist ziemlich kugelsicher, plattformunabhängig und da gibt es nie mehr als etwa 20 Einstellungen pro Client ist, ist dies nur 3 ziemlich niedrige Last db Anrufe -. Wahrscheinlich die schnellste Methode

Die Alternative einzelnen Reihen der Aktualisierung - Überprüfung auf Ausnahmen dann Einfügen - oder eine Kombination ist scheußlich Code, langsam und oft bricht, weil (wie oben erwähnt) nicht Standard-SQL-Ausnahmebehandlung von db db Wechsel - oder auch loslassen lösen .

 #This is pseudo-code - within the application:
 BEGIN TRANSACTION - get transaction lock
 SELECT all current name value pairs where id = $id into a hash record
 create a merge record from the current and update record
  (set intersection where shared keys in new win, and empty values in new are deleted).
 DELETE all name value pairs where id = $id
 COPY/INSERT merged records 
 END TRANSACTION

Nach dem PostgreSQL Dokumentation der INSERT Anweisung , Handhabung der ON DUPLICATE KEY Fall wird nicht unterstützt. Der Teil der Syntax ist eine proprietäre MySQL-Erweiterung.

CREATE OR REPLACE FUNCTION save_user(_id integer, _name character varying)
  RETURNS boolean AS
$BODY$
BEGIN
    UPDATE users SET name = _name WHERE id = _id;
    IF FOUND THEN
        RETURN true;
    END IF;
    BEGIN
        INSERT INTO users (id, name) VALUES (_id, _name);
    EXCEPTION WHEN OTHERS THEN
            UPDATE users SET name = _name WHERE id = _id;
        END;
    RETURN TRUE;
END;

$BODY$
  LANGUAGE plpgsql VOLATILE STRICT

Ich benutze diese Funktion merge

CREATE OR REPLACE FUNCTION merge_tabla(key INT, data TEXT)
  RETURNS void AS
$BODY$
BEGIN
    IF EXISTS(SELECT a FROM tabla WHERE a = key)
        THEN
            UPDATE tabla SET b = data WHERE a = key;
        RETURN;
    ELSE
        INSERT INTO tabla(a,b) VALUES (key, data);
        RETURN;
    END IF;
END;
$BODY$
LANGUAGE plpgsql

Für kleine Mengen Verschmelzung mit der obigen Funktion ist in Ordnung. Wenn Sie jedoch große Datenmengen wachsen zusammen, würde ich suchen in schlagen http: //mbk.projects.postgresql .org

Die aktuelle Best Practice, die ich bin mir dessen bewusst ist:

  1. Kopieren neue / aktualisierte Daten in temporäre Tabelle (sicher, oder Sie können INSERT tun, wenn die Kosten in Ordnung ist)
  2. Erwerben Sie sperren [optional] (beratender vorzuziehen Tabellensperren, IMO)
  3. Merge. (Der spaßige Teil)

UPDATE wird die Anzahl der geänderten Zeilen zurück. Wenn Sie JDBC (Java) verwenden, können Sie dann überprüfen Sie diesen Wert mit 0 und, wenn keine Zeilen betroffen, Feuer INSERT statt. Wenn Sie eine andere Programmiersprache verwenden, möglicherweise die Anzahl der geänderten Zeilen noch erhalten, Check-Dokumentation werden.

Das ist zwar nicht so elegant sein, aber Sie haben viel einfacher SQL, die mehr trivial ist von dem anrufenden Code zu verwenden. Anders, wenn Sie die zehn Zeilenskript in PL / PSQL schreiben, sollten Sie wahrscheinlich eine Einheit Test eine oder andere Art nur für sie allein.

Edit: Dies funktioniert nicht wie erwartet. Anders als die akzeptierte Antwort erzeugt diese einzigartige Schlüsselverletzungen, wenn zwei Prozesse wiederholt upsert_foo gleichzeitig aufrufen.

Eureka! Ich einen Weg gefunden, um es in einer Abfrage zu tun: Verwenden Sie UPDATE ... RETURNING zu testen, ob alle Zeilen betroffen sind:

CREATE TABLE foo (k INT PRIMARY KEY, v TEXT);

CREATE FUNCTION update_foo(k INT, v TEXT)
RETURNS SETOF INT AS $$
    UPDATE foo SET v = $2 WHERE k = $1 RETURNING $1
$$ LANGUAGE sql;

CREATE FUNCTION upsert_foo(k INT, v TEXT)
RETURNS VOID AS $$
    INSERT INTO foo
        SELECT $1, $2
        WHERE NOT EXISTS (SELECT update_foo($1, $2))
$$ LANGUAGE sql;

Die UPDATE muss in einem gesonderten Verfahren durchgeführt werden, denn leider ist dies ein Syntaxfehler ist:

... WHERE NOT EXISTS (UPDATE ...)

Jetzt funktioniert es wie gewünscht:

SELECT upsert_foo(1, 'hi');
SELECT upsert_foo(1, 'bye');
SELECT upsert_foo(3, 'hi');
SELECT upsert_foo(3, 'bye');
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top