Ich habe über verschiedene UPSERT
-Implementierungen in PostgreSQL gelesen, aber alle diese Lösungen sind relativ alt oder relativ exotisch (z. B. mit beschreibbarer CTE ).
Und ich bin überhaupt kein psql-Experte, um sofort herauszufinden, ob diese Lösungen alt sind, weil sie gut empfohlen werden, oder ob sie (fast alle) nur Spielzeugbeispiele sind, die nicht für die Verwendung in der Produktion geeignet sind.
Was ist der thread-sicherste Weg, um UPSERT in PostgreSQL zu implementieren?
PostgreSQL hat jetzt UPSERT .
Die bevorzugte Methode gemäß eine ähnliche StackOverflow-Frage ist derzeit die folgende:
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
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');
[~ # ~] Update [~ # ~] (20.08.2015):
Es gibt jetzt eine offizielle Implementierung für die Behandlung von Upsets mithilfe von ON CONFLICT DO UPDATE
(offizielle Dokumentation). Zum Zeitpunkt dieses Schreibens befindet sich diese Funktion derzeit in PostgreSQL 9.5 Alpha 2, das hier heruntergeladen werden kann: Postgres-Quellverzeichnisse .
Hier ein Beispiel unter der Annahme, dass item_id
Ihr Primärschlüssel ist:
INSERT INTO my_table
(item_id, price)
VALUES
(123456, 10.99)
ON
CONFLICT (item_id)
DO UPDATE SET
price = EXCLUDED.price
Originaler Beitrag ...
Hier ist eine Implementierung, auf die ich gestoßen bin, als ich einen Einblick in das Einfügen oder Aktualisieren erhalten wollte.
Die Definition von upsert_data
Besteht darin, die Werte in einer einzigen Ressource zu konsolidieren, anstatt den Preis und die item_id zweimal angeben zu müssen: Einmal für die Aktualisierung, erneut für die Einfügung.
WITH upsert_data AS (
SELECT
'19.99'::numeric(10,2) AS price,
'abcdefg'::character varying AS item_id
),
update_outcome AS (
UPDATE pricing_tbl
SET price = upsert_data.price
FROM upsert_data
WHERE pricing_tbl.item_id = upsert_data.item_id
RETURNING 'update'::text AS action, item_id
),
insert_outcome AS (
INSERT INTO
pricing_tbl
(price, item_id)
SELECT
upsert_data.price AS price,
upsert_data.item_id AS item_id
FROM upsert_data
WHERE NOT EXISTS (SELECT item_id FROM update_outcome LIMIT 1)
RETURNING 'insert'::text AS action, item_id
)
SELECT * FROM update_outcome UNION ALL SELECT * FROM insert_outcome
Wenn Ihnen die Verwendung von upsert_data
Nicht gefällt, finden Sie hier eine alternative Implementierung:
WITH update_outcome AS (
UPDATE pricing_tbl
SET price = '19.99'
WHERE pricing_tbl.item_id = 'abcdefg'
RETURNING 'update'::text AS action, item_id
),
insert_outcome AS (
INSERT INTO
pricing_tbl
(price, item_id)
SELECT
'19.99' AS price,
'abcdefg' AS item_id
WHERE NOT EXISTS (SELECT item_id FROM update_outcome LIMIT 1)
RETURNING 'insert'::text AS action, item_id
)
SELECT * FROM update_outcome UNION ALL SELECT * FROM insert_outcome
Dadurch erfahren Sie, ob das Einfügen oder Aktualisieren stattgefunden hat:
with "update_items" as (
-- Update statement here
update items set price = 3499, name = 'Uncle Bob'
where id = 1 returning *
)
-- Insert statement here
insert into items (price, name)
-- But make sure you put your values like so
select 3499, 'Uncle Bob'
where not exists ( select * from "update_items" );
Wenn das Update erfolgt, erhalten Sie eine Einfügung 0, andernfalls eine Einfügung 1 oder einen Fehler.