Upsert: einfügen oder ändern
Kurz gesagt: Ein Upsert fügt eine Zeile ein oder ändert die schon vorhandene mit ON CONFLICT … DO UPDATE, wenn eine eindeutige Spalte verletzt wird; DO NOTHING überspringt den Konflikt.
Problem
Der Kunde kommt wieder
Georgi Petrov ist nach Sofia gezogen. Er bestellt, ohne sich anzumelden, und das Formular schickt seinen Namen, seine E-Mail-Adresse und seine Stadt. Die Website versucht, ihn als neuen Kunden zu speichern:
Seine E-Mail-Adresse steht schon in der Datenbank, und sie ist eindeutig. Die Website muss entscheiden: neuer Kunde oder bestehender. Sie könnte erst mit einem SELECT prüfen und dann zwischen INSERT und UPDATE wählen. Aber das sind zwei Anweisungen, und dazwischen könnte ein anderer Besucher dieselbe E-Mail-Adresse speichern.
Die Idee
ON CONFLICT … DO UPDATE
ON CONFLICT sagt INSERT, was zu tun ist, wenn die Zeile eine eindeutige Spalte verletzt. Mit DO UPDATE ändert es die Zeile, die schon in der Tabelle steht:
Die zwei Zeilen gehen verschiedene Wege. Georgi ist da: Seine Zeile behält die Nummer 2 und das Anmeldedatum von 2025, nur die Stadt ändert sich. Vesela ist nicht da: Sie wird als Kundin 21 eingefügt.
excluded ist die Zeile, die die Anweisung einfügen wollte. excluded.city ist die Stadt aus VALUES, und city ohne etwas davor ist der alte Wert in der Tabelle.
Das heißt Upsert, aus „update“ und „insert“.
Genauer, für Neugierige
In PostgreSQL ist die Anweisung dieselbe: ON CONFLICT (email) DO UPDATE SET city = EXCLUDED.city. MySQL hat eine eigene Form: INSERT … ON DUPLICATE KEY UPDATE. SQL Server und Oracle benutzen MERGE, einen allgemeineren Befehl, der zwei Tabellen vergleicht und für jede Zeile entscheidet: einfügen, ändern oder löschen. PostgreSQL hat MERGE seit Version 15. SQLite hat kein MERGE.
Probier es aus
Eine neue Adresse
Elena Dimitrova (elena.d@example.com) ist nach Varna gezogen. Das Formular schickt ihren Namen, ihre E-Mail-Adresse, die Stadt Varna und das heutige Datum, 2026-10-25. Speichere sie so, dass sich nur die Stadt ändert. Ihr Anmeldedatum bleibt das alte.
Lösung
INSERT INTO customers (name, email, city, joined_at)
VALUES ('Elena Dimitrova', 'elena.d@example.com', 'Varna', '2026-10-25')
ON CONFLICT (email) DO UPDATE SET city = excluded.city;INSERT INTO customers (name, email, city, joined_at) VALUES ('Elena Dimitrova', 'elena.d@example.com', 'Varna', '2026-10-25') ON CONFLICT (email) DO UPDATE SET city = excluded.city. Wenn du auch joined_at = excluded.joined_at ergänzt, sieht Elena aus wie eine Kundin seit heute. Geprüft wird die ganze Tabelle customers.
Die Idee
Ware vom Lieferanten
Am Morgen kommt eine Lieferung: 30 Banitsas, 40 Gurken und ein neues Produkt, Ayran. Der Lieferant benutzt die Produktnummern des Shops, und der Ayran hat im Katalog schon die Nummer 26. Die bekannten Produkte müssen mehr Bestand bekommen, und das neue muss erscheinen:
Der Konflikt liegt bei id, dem Primärschlüssel. Für die Banitsa und die Gurken addiert products.stock + excluded.stock den gelieferten Bestand zum alten. Ihr Datum added_at bleibt das alte, weil es nicht in SET steht. Den Ayran gibt es nicht, deshalb wird er mit allen Spalten eingefügt.
Hier steht vor stock ein products., weil der Ausdruck zwei Bestände hat: den in der Tabelle und den aus der Lieferung.
Probier es aus
Mehr im Warenkorb
Georgi legt 2 Packungen Butter (Produkt 9, 3,20 €) in seine neue Bestellung 57. Wenn sie schon drin ist, muss die Menge steigen, und wenn nicht, muss eine Zeile dazukommen.
Lösung
INSERT INTO order_items (order_id, product_id, quantity, unit_price)
VALUES (57, 9, 2, 3.2)
ON CONFLICT (order_id, product_id) DO UPDATE SET quantity = quantity + excluded.quantity;INSERT INTO order_items (order_id, product_id, quantity, unit_price) VALUES (57, 9, 2, 3.2) ON CONFLICT (order_id, product_id) DO UPDATE SET quantity = quantity + excluded.quantity. In order_items ist der Schlüssel das Paar aus Bestellung und Produkt, der Konflikt liegt also bei beiden Spalten. Die Butter war 3 und wird 5.
Sieh es dir an
DO NOTHING
Manchmal soll sich bei einem Konflikt nichts ändern. Von einer Messe kommt eine Liste von Leuten, die Kunden werden wollen. Wer schon Kunde ist, wird übersprungen:
Maria bleibt, wie sie war, mit der Stadt Sofia. Vesela ist eine neue Kundin. Ohne ON CONFLICT hätte die ganze Anweisung bei Maria angehalten, und Vesela wäre nicht hineingekommen.
Genauer, für Neugierige
SQLite hat auch eine ältere eigene Form: INSERT OR IGNORE macht dasselbe wie DO NOTHING.
INSERT OR REPLACE sieht aus wie ein Upsert, ist aber keins. Bei einem Konflikt löscht es die alte Zeile und fügt eine neue ein. Teodora bekommt eine neue Nummer und ein neues Anmeldedatum:
Bei Georgi geht das Löschen gar nicht durch, weil seine Bestellungen auf die alte Zeile zeigen:
Nimm für ein Upsert also ON CONFLICT … DO UPDATE. Es ändert nur die Spalten in SET.
Übung
Die Lieferung
Der Lieferant schickt die Lieferung als Tabelle delivery: Produktnummer, Name, Kategorie, Preis und Menge. Für die Lektion ist die Tabelle schon in der Datenbank. Führ das SELECT am Anfang aus, um sie zu sehen.
Speichere die Lieferung in products. Bei den bekannten Produkten wird die gelieferte Menge zum Bestand addiert und der Preis des Lieferanten übernommen. Die neuen werden mit dem Datum 2026-10-25 eingefügt.
Die Lieferung in products:
Lösung
INSERT INTO products (id, name, category_id, price, stock, added_at)
SELECT product_id, name, category_id, price, quantity, '2026-10-25'
FROM delivery
WHERE true
ON CONFLICT (id) DO UPDATE SET
stock = products.stock + excluded.stock,
price = excluded.price;INSERT INTO products (id, name, category_id, price, stock, added_at) SELECT product_id, name, category_id, price, quantity, '2026-10-25' FROM delivery WHERE true ON CONFLICT (id) DO UPDATE SET stock = products.stock + excluded.stock, price = excluded.price. Die Butter kostet danach 3,40 € bei 40 auf Lager, vom Kaffee sind es 25, und der Grüne Tee ist das neue Produkt 26.
WHERE true sieht sinnlos aus, ist aber nötig. Ohne es liest SQLite ON als Anfang von JOIN … ON und bricht mit einem Fehler ab. Wenn die Zeilen für ein Upsert aus einem SELECT kommen, ergänze ein WHERE, auch wenn es nur WHERE true ist.
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Primärschlüssel und Fremdschlüssel · INSERT: Zeilen einfügen · UPDATE: Werte ändern · DELETE: Zeilen löschen
- Geht weiter in: Ereignisströme · Die Bäckerei, Etappe 4: die Änderungen
- Aufgaben zum Thema: Daten ändern