Wenn ein Datensatz entweder neu eingefügt oder bei einem Schlüsselkonflikt aktualisiert werden soll, ist in PostgreSQL UPSERT die saubere Standardlösung. Gemeint ist die Syntax INSERT ... ON CONFLICT, die Mehrfachdaten reduziert, parallele Zugriffe robuster macht und Code deutlich einfacher hält als vorherige Zwei-Schritt-Lösungen.
Für fortgeschrittene Entwickler:innen ist dabei vor allem wichtig, was tatsächlich einen Konflikt auslöst, wie sich EXCLUDED verhält und wo fachliche Fehler trotz korrekter Syntax entstehen. Genau diese Punkte entscheidet darüber, ob ein PostgreSQL-Statement nur „funktioniert“ oder im produktiven Alltag stabil bleibt.
Was ist SQL UPSERT in PostgreSQL?
In PostgreSQL bedeutet UPSERT: Ein INSERT versucht zuerst das Einfügen, und nur wenn ein passender Unique-Konflikt auftritt, greift die in ON CONFLICT definierte Alternative. Das vermeidet den unsauberen Ablauf „erst prüfen, dann schreiben“, der unter Last leicht zu doppelten Datensätzen führt.
Technisch basiert das Verhalten auf einem eindeutigen Constraint oder Index, etwa einem Primärschlüssel oder einer UNIQUE-Spalte. Ohne solche Eindeutigkeit kann PostgreSQL keinen Konflikt erkennen. Genau deshalb hängt eine robuste UPSERT-Strategie immer auch an sauber modellierten Datenbankregeln, wie sie bei sauberen Constraints entscheidend sind.
Ein einfaches Beispiel ist eine Tabelle für Benutzerprofile, in der die E-Mail-Adresse eindeutig sein muss. Existiert die E-Mail noch nicht, wird ein neuer Datensatz angelegt. Existiert sie bereits, kann PostgreSQL stattdessen gezielt einzelne Felder aktualisieren.
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
full_name TEXT NOT NULL,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
INSERT INTO users (email, full_name)
VALUES ('anna@example.com', 'Anna König')
ON CONFLICT (email)
DO UPDATE SET
full_name = EXCLUDED.full_name,
updated_at = NOW();
Wichtig ist hier EXCLUDED.full_name. EXCLUDED referenziert die Werte, die gerade eingefügt werden sollten, aber wegen des Konflikts nicht direkt geschrieben werden konnten. Das ist der zentrale Baustein für ein INSERT ON CONFLICT, das nicht nur still scheitert, sondern vorhandene Daten kontrolliert anpasst.
Wann ist DO NOTHING sinnvoll, wann DO UPDATE?
DO NOTHING eignet sich, wenn ein Konflikt fachlich akzeptabel ist und vorhandene Daten unverändert bleiben sollen. DO UPDATE ist die bessere Wahl, wenn neue Informationen einen bestehenden Datensatz bewusst fortschreiben sollen.
Der Unterschied ist nicht nur syntaktisch, sondern fachlich relevant. Bei einem Import von Tags, Kategorien oder Feature-Flags genügt oft DO NOTHING, weil ein zweiter identischer Eintrag keinen Mehrwert bringt. Bei Benutzerprofilen, Lagerbeständen oder Synchronisationsdaten soll ein bestehender Satz dagegen häufig aktualisiert werden.
| Variante | Sinnvoll für | Risiko |
|---|---|---|
DO NOTHING |
Idempotente Inserts, Lookup-Daten, Duplikate ignorieren | Neue Fachinformationen gehen still verloren |
DO UPDATE |
Profile, Zähler, Sync-Daten, Status-Updates | Bestehende Daten werden unbeabsichtigt überschrieben |
Gerade bei APIs ist diese Entscheidung eng mit Idempotenz verknüpft. Wenn ein Request mehrfach eintreffen darf, ohne fachlich falsche Nebenwirkungen zu erzeugen, hilft eine klare Konfliktstrategie. In solchen Fällen passt oft auch der Blick auf Idempotency Keys, weil Datenbank- und API-Ebene dann sauber zusammenspielen.
Ein häufiger Fehler ist, reflexhaft immer DO UPDATE zu verwenden. Das kann fachlich falsch sein, wenn alte Werte bewusst erhalten bleiben sollen, etwa bei einem ersten Importzeitpunkt oder einer initialen Zustimmung. Die Datenbank tut dann zwar exakt, was angeordnet wurde, aber nicht das, was das Geschäftsmodell verlangt.
- Prüfe zuerst, ob ein Konflikt ignoriert oder fachlich verarbeitet werden soll.
- Lege fest, welche Spalten bei einem Konflikt überschrieben werden dürfen und welche nicht.
- Nutze
DO NOTHINGfür idempotente Schreibvorgänge ohne Änderungsbedarf. - Nutze
DO UPDATEnur dann, wenn das Überschreiben bestehender Daten beabsichtigt ist. - Verlasse dich nie auf UPSERT ohne passenden Unique-Constraint oder Unique-Index.
Welche Constraints braucht ON CONFLICT wirklich?
ON CONFLICT funktioniert nur zuverlässig, wenn PostgreSQL einen eindeutigen Konfliktpunkt kennt. In der Praxis ist das fast immer ein Primärschlüssel, ein UNIQUE-Constraint oder ein passender Unique-Index.
Viele Missverständnisse entstehen, weil Entwickler:innen die UPSERT-Syntax als Logikwerkzeug sehen, obwohl sie in Wirklichkeit an Datenmodellierung gebunden ist. Wer etwa Benutzer nur über email identifiziert, muss diese Spalte auch eindeutig absichern. Ohne diese Regel kann dieselbe E-Mail mehrfach gespeichert werden, und das Problem liegt nicht im SQL-Statement, sondern im Schema.
PostgreSQL erlaubt dabei mehrere Varianten. Du kannst den Konflikt über eine Spaltenliste wie ON CONFLICT (email) angeben oder direkt einen benannten Constraint referenzieren. Letzteres ist oft lesbarer, wenn zusammengesetzte Schlüssel im Spiel sind.
CREATE TABLE user_settings (
user_id BIGINT NOT NULL,
setting_key TEXT NOT NULL,
setting_value TEXT NOT NULL,
PRIMARY KEY (user_id, setting_key)
);
INSERT INTO user_settings (user_id, setting_key, setting_value)
VALUES (42, 'theme', 'dark')
ON CONFLICT (user_id, setting_key)
DO UPDATE SET
setting_value = EXCLUDED.setting_value;
Dieses Muster ist typisch für Konfigurations-, Mapping- oder Zuordnungstabellen. Ein zusammengesetzter Schlüssel bildet die fachliche Eindeutigkeit genauer ab als eine künstliche ID allein. Wenn solche Modelle komplexer werden, helfen oft auch klare Normalisierungsschritte, weil Eindeutigkeit dann aus der Domäne statt aus Workarounds entsteht.
Relevant ist außerdem, dass PostgreSQL nicht irgendeinen Konflikt meint, sondern einen Konflikt mit einer eindeutigen Regel. Ein fehlerhaftes ON CONFLICT ist deshalb oft ein Hinweis auf ein unklar modelliertes Schema. Wer UPSERT sicher nutzen will, sollte Constraints nicht als lästiges Extra behandeln, sondern als Voraussetzung für korrekte Schreiboperationen.
Wie aktualisiert man nur ausgewählte Felder sicher?
Ein gutes UPSERT überschreibt nicht blind alle Spalten, sondern nur die Werte, die fachlich wirklich ersetzt werden sollen. Genau hier trennt sich robuste Datenpflege von bequemer, aber riskanter Standardsyntax.
Besonders wichtig ist das bei Zeitstempeln, Statusfeldern und manuell gepflegten Informationen. Wer pauschal jede Spalte mit EXCLUDED überschreibt, löscht leicht bestehende Datenqualität. Ein klassisches Beispiel ist ein manuell korrigierter Anzeigename, der durch einen späteren Import wieder mit einem alten Rohwert ersetzt wird.
PostgreSQL erlaubt, Update-Logik sehr präzise auszudrücken. Du kannst bestehende Werte beibehalten, nur bei bestimmten Bedingungen aktualisieren oder Funktionen wie COALESCE einsetzen. Dadurch bleibt das Statement kompakt, ohne unkontrolliert zu werden.
INSERT INTO users (email, full_name, updated_at)
VALUES ('anna@example.com', 'Anna K.', NOW())
ON CONFLICT (email)
DO UPDATE SET
full_name = CASE
WHEN users.full_name <> EXCLUDED.full_name THEN EXCLUDED.full_name
ELSE users.full_name
END,
updated_at = NOW()
WHERE users.full_name IS DISTINCT FROM EXCLUDED.full_name;
Das WHERE hinter DO UPDATE ist in der Praxis sehr nützlich. Es verhindert unnötige Updates, reduziert Write-Last und lässt Trigger oder Replikationsmechanismen nur dann anspringen, wenn sich tatsächlich etwas geändert hat. Gerade bei stark frequentierten Tabellen ist das relevanter, als es auf den ersten Blick wirkt.
Eine sichere Regel lautet daher: nur die Felder aktualisieren, die eindeutig aus der neuen Eingabe stammen dürfen. Für alles andere sollte bewusst entschieden werden, ob bestehende Werte Vorrang haben, ob zusammengeführt werden muss oder ob ein Konflikt sogar ein Fehlerfall sein sollte.
Warum ersetzt UPSERT keine Transaktion und keine Fachlogik?
UPSERT löst den Konflikt beim Schreiben eines einzelnen Datensatzes, aber nicht automatisch das gesamte Konsistenzproblem einer Anwendung. Sobald mehrere Tabellen, Folgeschritte oder fachliche Prüfungen beteiligt sind, bleiben Transaktionen und Anwendungslogik unverzichtbar.
Ein typischer Irrtum ist die Annahme, ein einziges INSERT ... ON CONFLICT mache einen kompletten Workflow atomar. Das stimmt nur für genau dieses Statement. Wenn danach noch ein Audit-Log geschrieben, ein Bestand angepasst oder ein Ereignis veröffentlicht wird, kann der Gesamtprozess weiterhin inkonsistent werden, wenn kein Transaktionsrahmen existiert.
Gerade bei Bestellungen, Abonnements oder Abrechnungen sollte UPSERT daher als Baustein gesehen werden, nicht als vollständiges Sicherheitsnetz. Mehrstufige Schreibvorgänge werden erst mit sauber gesetzten SQL-Transaktionen wirklich belastbar, weil Commit und Rollback dann die gesamte Operation absichern.
Auch fachlich kann UPSERT zu kurz greifen. Wenn etwa ein Konflikt nicht „aktualisieren“, sondern „prüfen und ablehnen“ bedeuten soll, ist ein automatisches DO UPDATE die falsche Entscheidung. Die Datenbank kennt nur technische Regeln; ob eine Änderung erlaubt ist, bleibt eine Fachfrage der Anwendung.
Typische Fehler bei UPSERT und wie man sie vermeidet
Die häufigsten Probleme bei UPSERT liegen nicht in der Syntax, sondern in falschen Annahmen über Datenmodell, Konfliktursache und Update-Semantik. Wer diese drei Ebenen trennt, vermeidet die meisten Produktionsfehler.
Fehler Nummer eins ist ein fehlender oder unpassender Unique-Constraint. Dann wird gar kein Konflikt erkannt, obwohl die Anwendung fachlich Duplikate erwartet. Fehler Nummer zwei ist blindes Überschreiben aller Spalten per EXCLUDED, obwohl einige Felder geschützt oder manuell gepflegt sind.
Ebenso problematisch sind unnötige Updates. Sie erzeugen zusätzliche Schreiblast, verändern updated_at-Werte ohne echte Änderung und können Trigger, CDC-Pipelines oder Replikationsprozesse unnötig belasten. Ein präzises WHERE im Update-Zweig ist deshalb oft kein Feinschliff, sondern echte Stabilitätsarbeit.
Was ist der Unterschied zwischen UPSERT und MERGE?
PostgreSQL ist in der Praxis lange vor allem über INSERT ... ON CONFLICT genutzt worden, weil dieses Muster für viele Anwendungsfälle kompakt und gut lesbar ist. MERGE kann breiter formulierte Abgleiche modellieren, ist aber für klassische Einfügen-oder-Aktualisieren-Szenarien oft schwergewichtiger.
Kann UPSERT Race Conditions vollständig verhindern?
UPSERT verhindert vor allem das Rennen zwischen „existiert schon?“ und „jetzt einfügen“ bei eindeutigen Schlüsseln. Es löst aber nicht automatisch alle konkurrierenden Fachkonflikte, etwa wenn mehrere Statements nacheinander logisch zusammengehören.
Ist DO NOTHING immer harmlos?
Nein, denn ignorierte Konflikte können auch verlorene Informationen bedeuten. Wenn eine neu eingetroffene Eingabe eigentlich den bestehenden Datensatz ergänzen oder korrigieren sollte, ist stilles Überspringen fachlich falsch, obwohl das SQL technisch korrekt bleibt.
Sauber eingesetztes UPSERT macht Schreiboperationen in PostgreSQL robuster, weil Einfügen und Konfliktbehandlung in einem Statement zusammenfallen. Der entscheidende Punkt liegt aber nicht in der Syntax allein, sondern in eindeutigen Constraints, bewusst gewählten Update-Regeln und klarer Fachlogik. INSERT ... ON CONFLICT ist damit kein Trick, sondern ein präzises Werkzeug für konsistente Datenänderungen. Wer es so behandelt, reduziert Duplikate, vermeidet unnötige Race Conditions und hält Datenbankcode deutlich wartbarer.

