UPSERT'yi PostgreSQL'de uygulamanın aptalca yolu


40

UPSERTPostgreSQL'de farklı uygulamaları okudum , ancak bu çözümlerin tümü nispeten eski veya nispeten egzotiktir ( örneğin, yazılabilir CTE kullanarak ).

Ve ben hemen hemen bulmak için hiç bir psql uzmanı değilim, bu çözümlerin eski olup olmadıklarını, çünkü iyi bir şekilde önerildiklerini ya da (neredeyse hepsi olduğu gibi) eskiden üretim kullanımına uygun olmayan oyuncak örnekleri olduğunu düşünüyorum.

UPSERT'yi PostgreSQL'de uygulamanın en güvenli yolu nedir?

Yanıtlar:


23

PostgreSQL'in şimdi UPSERT'si var .


Benzer bir StackOverflow sorusuna göre tercih edilen yöntem şu anda aşağıdaki gibidir:

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');

7
Yazılabilir bir CTE kullanmayı tercih ederim: stackoverflow.com/a/8702291/330315
a_horse_with_no_name

Bir fonksiyona karşı yazılabilir CTE'nin avantajı nedir?
François Beausoleil,

1
@ François bir şey için hız. Bir CTE kullanarak bir kez veritabanına çarptınız. Bu şekilde yaparsanız iki veya daha fazla kez vurabilirsiniz. Ayrıca, en iyileştirici pl / pgsql prosedürlerini saf SQL kodu kadar verimli bir şekilde optimize edemez.
Adam Mackler

1
@ François Başka bir şey için, eşzamanlılık. Yukarıdaki örnekte birden fazla SQL ifadesi bulunduğundan, yarış koşulları hakkında endişelenmeniz gerekir (klugey döngüsünün nedeni). Tek bir SQL ifadesi atomik olacaktır. Bkz bu bağlantıyı
Adam Mackler

1
FrançoisBeausoleil @ bakın burada ve burada niçin. Temel olarak, bir yeniden deneme döngüsü olmadan, serileştirmeniz gerekir veya doğal yarış koşullarından dolayı arıza olasılığınız olur.
Jack Douglas

27

GÜNCELLEME (2015-08-20):

Artık ON CONFLICT DO UPDATE(resmi belgeler) kullanımıyla teşvikleri ele almak için resmi bir uygulama var . Bu yazının yazıldığı sırada, bu özellik şu anda burada indirilebilecek PostgreSQL 9.5 Alpha 2 adresinde bulunmaktadır : Postgres kaynak dizinleri .

Örnek olarak, item_idBirincil Anahtarınız olduğunu farz edelim :

INSERT INTO my_table
    (item_id, price)
VALUES
    (123456, 10.99)
ON
    CONFLICT (item_id)
DO UPDATE SET
    price = EXCLUDED.price

Orijinal Gönderi ...

İşte bir ekleme veya güncelleme olup olmadığına ilişkin görünürlük elde etmek istediğimde uyguladığım bir uygulama.

Bunun tanımı upsert_data, fiyatı ve item_id değerini iki kez belirtmek yerine, değerleri tek bir kaynakta birleştirmektir: Güncelleme için bir kez, ekleme için tekrar.

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

Kullanımını beğenmiyorsanız, upsert_dataalternatif bir uygulama:

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

Nasıl gerçekleştirir?
jb.

1
@jb. istediğim kadar değil. Düz uçlar yerine, önemli performans cezaları göreceksiniz. Bununla birlikte, daha küçük gruplar için (örneğin 1000 veya daha az), bu örnek iyi sonuç vermelidir.
Joshua Burns,

0

Bu, ekleme veya güncelleştirmenin olup olmadığını size bildirir:

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" );

Güncelleme olursa, 0 eki alırsınız, aksi halde 1 ekler veya hata alırsınız.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.