postgresql ile “insert ignore” ve “yinelenen anahtar güncellemesinde” (sql merge) nasıl taklit edilir?


140

Bazı SQL sunucuları, INSERTbirincil / benzersiz anahtar kısıtlamasını ihlal edecekse atlanan bir özelliğe sahiptir . Örneğin, MySQL var INSERT IGNORE.

Taklit etmenin en iyi yolu nedir INSERT IGNOREve ON DUPLICATE KEY UPDATEPostgreSQL ile?




6
9.5 itibariyle, doğal olarak mümkündür: stackoverflow.com/a/34639631/4418
warren

MySQL'i taklit etmek: ON DUPLICATE KEY UPDATEPgSQL 9.5'te hala biraz imkansızdır, çünkü PgSQL ON CLAUSEeşdeğeri kısıtlama adını vermenizi gerektirirken, MySQL herhangi bir kısıtlamayı tanımlamak zorunda kalmadan yakalayabilir. Bu, sorguları yeniden yazmadan bu özelliği "taklit etmemi" önler.
NeverEndingQueue

Yanıtlar:


35

Bir GÜNCELLEME yapmaya çalışın. Var olmadığı anlamına gelen herhangi bir satırı değiştirmezse, bir ekleme yapın. Açıkçası, bunu bir işlemin içinde yaparsınız.

Ekstra kodu istemci tarafına koymak istemiyorsanız, elbette bunu bir fonksiyona sarabilirsiniz. Bu düşüncede çok nadir görülen yarış durumu için de bir döngüye ihtiyacınız var.

Belgelerde bunun bir örneği var: http://www.postgresql.org/docs/9.3/static/plpgsql-control-structures.html , altta örnek 40-2.

Bu genellikle en kolay yoldur. Kurallarla biraz sihir yapabilirsiniz, ama muhtemelen çok daha karışık olacak. Ben her gün bu işleve sarmayı tavsiye ederim.

Bu, tek satır veya birkaç satır değeri için geçerlidir. Örneğin bir alt sorgudan büyük miktarda satırla uğraşıyorsanız, bunu bir tanesi INSERT ve diğeri UPDATE için olmak üzere iki sorguya bölmeniz en iyisidir (elbette uygun birleştirme / alt seçim olarak - ana kodunuzu yazmanıza gerek yoktur) iki kez filtre uygulayın)


4
"Eğer büyük miktarda satır ile uğraşıyorsanız" tam olarak benim durumum. Toplu güncelleme / satır eklemek istiyorum ve mysql ile bunu herhangi bir döngü olmadan tek bir sorgu ile yapabilirsiniz. Şimdi bu postgresql ile de mümkün olup olmadığını merak ediyorum: toplu güncelleme VEYA eklemek için sadece bir sorgu kullanmak. "Birini INSERT ve diğeri UPDATE için olmak üzere iki sorguya bölmek en iyisidir" diyorsunuz, ancak yinelenen anahtarlara hata atmayan bir ek nasıl yapabilirim? (yani. "
INSNT

4
Magnus, şu şekilde bir sorgu kullandığınız anlamına geliyordu: "işlem başlat; testten false olarak geçici * geçici tablo oluştur; burada yanlıştan seç; geçici olarak 'data_file.csv' den kopyala; test.id = temporary_table.id; kimliğe girmediğinde (testten id seçin) "
Tometzky

25
Güncelleme: PostgreSQL 9.5 ile artık bu kadar basit INSERT ... ON CONFLICT DO NOTHING;. Ayrıca bkz . Stackoverflow.com/a/34639631/2091700 .
Alphaaa

Önemli, SQL standardı MERGE, birincisini almadığınız sürece eşzamanlı olarak güvenli bir destek değildirLOCK TABLE . İnsanlar bu şekilde kullanıyor, ama bu yanlış.
Craig Ringer

1
V9.5 ile şimdi bir 'yerli' özellik, bu yüzden lütfen @Alphaaa'nın yorumunu kontrol edin (sadece cevabı tanıtan yorumu reklam verin)
Camilo Delvasto

178

PostgreSQL 9.5 ile bu artık yerel işlevler ( MySQL'in birkaç yıldır sahip olduğu gibi ):

INSERT ... ÇATIŞMA ÜZERİNE HİÇBİR ŞEY / GÜNCELLEME ("UPSERT")

9.5 "UPSERT" işlemleri için destek getirir. INSERT, ON CONFLICT DO UPDATE / IGNORE yantümcesini kabul edecek şekilde genişletilmiştir. Bu madde, yinelenen bir ihlal olması durumunda gerçekleştirilecek alternatif bir eylemi belirtir.

...

Yeni sözdizimi için başka örnek:

INSERT INTO user_logins (username, logins)
VALUES ('Naomi',1),('James',1) 
ON CONFLICT (username)
DO UPDATE SET logins = user_logins.logins + EXCLUDED.logins;

100

Düzenleme: warren'ın cevabını kaçırırsanız, PG9.5 artık bu yerel; yükseltme zamanı!


Bill Karwin'in cevabına dayanarak, kural tabanlı bir yaklaşımın nasıl görüneceğini belirtmek için (aynı DB'deki ve çok sütunlu bir birincil anahtarla başka bir şemadan aktarma):

CREATE RULE "my_table_on_duplicate_ignore" AS ON INSERT TO "my_table"
  WHERE EXISTS(SELECT 1 FROM my_table 
                WHERE (pk_col_1, pk_col_2)=(NEW.pk_col_1, NEW.pk_col_2))
  DO INSTEAD NOTHING;
INSERT INTO my_table SELECT * FROM another_schema.my_table WHERE some_cond;
DROP RULE "my_table_on_duplicate_ignore" ON "my_table";

Not: Kural INSERT, kural kaldırılana kadar tüm işlemler için geçerlidir , bu nedenle oldukça geçici değildir.


@ema another_schema.my_tablesınırlarına göre yinelenen içeriyorsa my_table?
EoghanM

2
@EoghanM Kuralı postgresql 9.3'te test ettim ve yine de INSERT INTO "my_table" (a, b), (a, b); (Bu satırın (a, b) "my_table" dosyasında henüz mevcut olmadığını varsayarsak.)
sema

@sema, gotcha - bu, kuralın başlangıçta eklenecek tüm verilerin üzerinde yürütüldüğü ve her satır eklendikten sonra yeniden yürütülmediği anlamına gelmelidir. Bir yaklaşım, verilerinizi önce herhangi bir kısıtlaması olmayan başka bir geçici tabloya eklemek ve sonra INSERT INTO "my_table" SELECT DISTINCT ON (pk_col_1, pk_col_2) * FROM the_tmp_table;
yapmaktır

@EoghanM Başka bir yaklaşım, yinelenen kısıtlamaları geçici olarak gevşetmek ve ekleme sırasında yinelenenleri kabul etmek, ancak daha sonra yinelenenleri kaldırmaktırDELETE FROM my_table WHERE ctid IN (SELECT ctid FROM (SELECT ctid,ROW_NUMBER() OVER (PARTITION BY pk_col_1,pk_col_2) AS rn FROM my_table) AS dups WHERE dups.rn > 1);
sema

@Sema tarafından açıklanan sorunu yaşıyorum. Bir kesici uç (a, b), (a, b) yaparsam bir hata atar. Bu durumda da hataları bastırmanın bir yolu var mı?
Diogo Melo

35

Postgres 9.5 veya üstü olanlarınız için yeni ON CONFLICT DO NOTHING sözdizimi çalışmalıdır:

INSERT INTO target_table (field_one, field_two, field_three ) 
SELECT field_one, field_two, field_three
FROM source_table
ON CONFLICT (field_one) DO NOTHING;

Daha önceki bir sürüme sahip olanlarımız için, bu doğru katılım bunun yerine çalışacaktır:

INSERT INTO target_table (field_one, field_two, field_three )
SELECT source_table.field_one, source_table.field_two, source_table.field_three
FROM source_table 
LEFT JOIN target_table ON source_table.field_one = target_table.field_one
WHERE target_table.field_one IS NULL;

İkinci yaklaşım, eşzamanlı bir ortamda büyük bir ekleme yaparken işe yaramaz. Bir olsun Unique violation: 7 ERROR: duplicate key value violates unique constraintzaman target_tablebaşka bir satır içine sokulmuş olan ise bu sorgu infaz ediliyordu onların tuşları, gerçekten, birbirlerini yinelerseniz. Kilitlemenin target_tableyardımcı olacağına inanıyorum , ancak eşzamanlılık açıkça acı çekecek.
G. Kashtanov

1
ON CONFLICT (field_one) DO NOTHINGcevabın en iyi yanı.
Abel Callejo

24

Ekleme yoksay mantığını almak için aşağıdaki gibi bir şey yapabilirsiniz. Ben sadece en iyi çalıştı değişmez değerlerin seçme bir ifadeden ekleme bulundu, o zaman yinelenen anahtarları bir NOT EXISTS yan tümcesi ile maskeleyebilirsiniz. Yinelenen mantıkla ilgili güncellemeyi almak için pl / pgsql döngüsünün gerekli olduğundan şüpheleniyorum.

INSERT INTO manager.vin_manufacturer
(SELECT * FROM( VALUES
  ('935',' Citroën Brazil','Citroën'),
  ('ABC', 'Toyota', 'Toyota'),
  ('ZOM',' OM','OM')
  ) as tmp (vin_manufacturer_id, manufacturer_desc, make_desc)
  WHERE NOT EXISTS (
    --ignore anything that has already been inserted
    SELECT 1 FROM manager.vin_manufacturer m where m.vin_manufacturer_id = tmp.vin_manufacturer_id)
)

Tmp yinelenen bir satır içeriyorsa ne olur?
Henley Chiu

Her zaman farklı anahtar kelimelerle seçim yapabilirsiniz.
Keyo

5
Tıpkı bir FYI gibi, farklı işlemler diğer işlemlerden yeni eklenen verileri göremediğinden, "NEREDEN YOK" hilesi birden fazla işlemde çalışmaz.
Dave Johansen

21
INSERT INTO mytable(col1,col2) 
    SELECT 'val1','val2' 
    WHERE NOT EXISTS (SELECT 1 FROM mytable WHERE col1='val1')

Aynı işlemi yapmaya çalışan birden fazla işlemin etkisi nedir? Mevcut olmayan yerde yürütme ile başka bir işlemi yürüten insert arasında bir satır eklemek mümkün müdür? Ve Postgres bunu engelleyebiliyorsa, postgres buna isabet ettiklerinde tüm bu işlemler arasında bir senkronizasyon noktası getirmiyor mu?
7αrτhικ

Yeni eklenen veriler diğer işlemler tarafından görülmediğinden, bu birden çok işlemle çalışmaz.
Dave Johansen

12

PostgreSQL'in kural adı verilen bir şema nesnesini desteklediği anlaşılıyor .

http://www.postgresql.org/docs/current/static/rules-update.html

ON INSERTBelirli bir tablo için, NOTHINGverilen birincil anahtar değerine sahip bir satır varsa bunu veya başka bir şekilde belirtilen birincil anahtar değerine sahip bir satır varsa UPDATEyerine bir kural oluşturabilirsiniz INSERT.

Bunu kendim denemedim, bu yüzden deneyimden konuşamıyorum veya bir örnek sunamıyorum.


1
iyi anladıysam bu kurallar her deyim çağrıldığında çalıştırılır tetikleyiciler vardır. kuralı yalnızca bir sorgu için uygulamak istersem ne olur? kuralı oluşturup hemen yok etmeliyim? (yarış koşulları ne olacak?)
gpilotino

3
Evet, aynı sorularım da olurdu. Kural mekanizması PostgreSQL'de MySQL'in INSERT IGNORE veya ON DUPLICATE KEY UPDATE'e bulabildiğim en yakın şey. "Yinelenen anahtar güncellemesinde postgresql" için google yaparsak, bir Kural yalnızca ad hoc olarak değil, herhangi bir INSERT için geçerli olsa bile, Kural mekanizmasını öneren diğer kişileri bulursunuz.
Bill Karwin

4
PostgreSQL, işlemsel DDL'yi destekler; başka bir deyişle, bir kural oluşturup tek bir işlem içine bırakırsanız, kural bu işlemin dışında hiçbir zaman görünmez (ve dolayısıyla hiçbir etkisi olmayacaktır).
cdhowie

6

@Hanmari'nin yorumunda belirtildiği gibi. bir postgres tablolarına eklerken, on çakışma (..) hiçbir şey yapmaz yinelenen veri eklemek için kullanmak için en iyi kod değildir .:

query = "INSERT INTO db_table_name(column_name)
         VALUES(%s) ON CONFLICT (column_name) DO NOTHING;"

ON CONFLICT kod satırı, insert deyiminin yine de veri satırları eklemesine izin verecektir. Sorgu ve değerler kodu, Excel'den postgres db tablosuna eklenen tarihin bir örneğidir. Kimlik alanının benzersiz olduğundan emin olmak için kullandığım postgres tablosuna kısıtlamalar ekledim. Aynı veri satırlarında bir silme çalıştırmak yerine, 1'den başlayarak kimlik sütununu yeniden numaralandıran bir sql kodu satırı eklerim. Örnek:

q = 'ALTER id_column serial RESTART WITH 1'

Verilerimin bir kimlik alanı varsa, bunu birincil kimlik / seri kimliği olarak kullanmıyorum, bir kimlik sütunu oluşturuyorum ve seri olarak ayarlıyorum. Umarım bu bilgiler herkese faydalı olur. * Yazılım geliştirme / kodlama konusunda üniversite derecem yok. Kodlamada bildiğim her şey, kendi başıma çalışıyorum.


bu bileşik benzersiz endekslerde işe yaramaz!
Nulik

4

Bu çözüm kurallar kullanmaktan kaçınır:

BEGIN
   INSERT INTO tableA (unique_column,c2,c3) VALUES (1,2,3);
EXCEPTION 
   WHEN unique_violation THEN
     UPDATE tableA SET c2 = 2, c3 = 3 WHERE unique_column = 1;
END;

ancak performans dezavantajı vardır (bkz. PostgreSQL.org ):

Bir EXCEPTION deyimi içeren bir bloğun girilmesi ve çıkması, bir bloka sahip olmayan bir bloktan önemli ölçüde daha pahalıdır. Bu nedenle, EXCEPTION'ı gerek kalmadan kullanmayın.


1

Toplu olarak, eklemeden önceki satırı her zaman silebilirsiniz. Var olmayan bir satırın silinmesi hataya neden olmaz, bu nedenle güvenle atlanır.


2
Bu yaklaşım garip yarış koşullarına oldukça eğilimli olacak, bunu tavsiye etmem ...
Steven Schlansker

1
+1 Bu kolay ve geneldir. Dikkatli kullanılırsa bu aslında basit bir çözüm olabilir.
Wouter van Nifterick

1
Ekleme sonrası mevcut veriler değiştirildiğinde (ancak yinelenen anahtarda değil) ve güncellemeleri saklamak istediğimizde de çalışmaz. Bu, üretim, KG, dev ve test sistemlerinde çalışan db güncellemeleri gibi biraz farklı sistemler için yazılmış SQL komut dosyaları olduğunda senaryodur.
Hanno Fietz

1
Yabancı anahtar, DEFERRABLE INITIALLY DEFERREDbayraklarla oluşturursanız sorunsuz olabilir .
temoto

-1

Veri içe aktarma komut dosyaları için, "VARSA DEĞİL" yerine, bir şekilde, yine de çalışan biraz garip bir formülasyon vardır:

DO
$do$
BEGIN
PERFORM id
FROM whatever_table;

IF NOT FOUND THEN
-- INSERT stuff
END IF;
END
$do$;
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.