22.012014

PostgreSQL: INSERT und UPDATE in einem Statement

Jeder Softwareentwickler, der schon einmal Datenbankoperationen durchführen musste, stand sicherlich bereits vor dem Problem, dass ein Datensatz entweder in die Datenbank eingetragen, oder aber aktualisiert werden soll.

Im Folgenden werde ich ein Beispiel für PostgreSQL vorstellen,  mit dem diese Problemstellung in einem SQL-Statement behandelt werden kann. Seit PostgreSQL 9.1 steht hierfür der Befehl UPSERT zur Verfügung. UPSERT bedeutet, dass ein UPDATE auf einen bestehenden Datensatz durchgeführt wird, oder aber  ein INSERT, wenn er noch nicht vorhanden ist. Als Beispiel nehmen wir jetzt einfach mal an, dass in einer Datenbanktabelle aufgezeichnet werden soll, wie oft nach dem Keyword "John Doe" gesucht wurde. Im ersten Schritt würden wir jetzt erstmal ein Insert Statment ausführen, damit der Datensatz in der Datenbank steht:

INSERT INTO search_tracking (keyword, count) SELECT 'John Doe', 1;

Wird jetzt aber erneut eine Suche mit diesem Keyword durchgeführt, so müssen wir den Datensatz aktualisieren:

UPDATE search_tracking SET count=count+1 WHERE keyword = 'John Doe';

Somit müssen wir in unserer Anwendung also immer entscheiden, ob wir nun ein INSERT oder UPDATE durchführen wollen. Wie anfangs aber bereits geschrieben, wollen wir das ja vermeiden. Also müssen wir die zwei Statements kombinieren, so dass wir nur noch eines haben, unser gewünschtes UPSERT, das wie folgt ausschaut:

WITH upsert AS (UPDATE search_tracking SET count=count+1 WHERE keyword = 'John Doe' RETURNING *) INSERT INTO search_tracking (keyword, count) SELECT 'John Doe', 1 WHERE NOT EXISTS(SELECT * FROM upsert);

Um jetzt noch eventuelle Probleme auszuschließen, die auftreten könnten, sollten mehrere UPSERTs gleichzeitig ausgeführt werden, so kann man das Ganze auch noch mit einem LOCKING versehen. Das würde dann wie folgt aussehen:

BEGIN;
LOCK TABLE search_tracking IN SHARE ROW EXCLUSIVE MODE;
WITH upsert AS (UPDATE search_tracking SET count=count+1 WHERE keyword = 'John Doe' RETURNING *) INSERT INTO search_tracking (keyword, count) SELECT 'John Doe', 1 WHERE NOT EXISTS(SELECT * FROM upsert);
COMMIT;

Viel Spass beim Optimieren der eigenen Programmlogik und SQL-Statements.