ON CONFLICT yan tümcesinde birden çok çatışma_ hedefi kullanın


95

Masanın iki sütun var col1, col2ikisi de benzersiz endeksli (col1 tektir ve böylece col2 olan) bulunmaktadır.

Bu tabloya eklemem, ON CONFLICTsözdizimi kullanmam ve diğer sütunları güncellemem gerekiyor, ancak her iki sütunu da conflict_targetcümlede kullanamıyorum .

İşe yarıyor:

INSERT INTO table
...
ON CONFLICT ( col1 ) 
DO UPDATE 
SET 
-- update needed columns here

Ancak bunun birkaç sütun için nasıl yapılacağı, bunun gibi bir şey:

...
ON CONFLICT ( col1, col2 )
DO UPDATE 
SET 
....

4
"col1, col2, her ikisi de benzersiz dizinlidir." bu col1'in benzersiz olduğu ve col2'nin benzersiz olduğu anlamına mı geliyor yoksa col1, col2'nin kombinasyonları benzersiz mi?
e4c5

1
bu
col1'in

Yanıtlar:


48

Örnek bir tablo ve veriler

CREATE TABLE dupes(col1 int primary key, col2 int, col3 text,
   CONSTRAINT col2_unique UNIQUE (col2)
);

INSERT INTO dupes values(1,1,'a'),(2,2,'b');

Sorunu yeniden üretmek

INSERT INTO dupes values(3,2,'c')
ON CONFLICT (col1) DO UPDATE SET col3 = 'c', col2 = 2

Buna Q1 diyelim. Sonuç

ERROR:  duplicate key value violates unique constraint "col2_unique"
DETAIL:  Key (col2)=(2) already exists.

Belgeler ne diyor

çatışma_target benzersiz dizin çıkarımı gerçekleştirebilir. Çıkarım gerçekleştirirken, bir veya daha fazla index_column_name sütunundan ve / veya index_expression ifadesinden ve isteğe bağlı bir index_predicate'den oluşur. Sıraya bakılmaksızın, tam olarak çakışan hedefle belirtilen sütunları / ifadeleri içeren tüm tablo_adı benzersiz dizinler, hakem dizinleri olarak çıkarılır (seçilir). Bir index_predicate belirtilmişse, çıkarım için ek bir gereklilik olarak hakem indekslerini karşılaması gerekir.

Bu, aşağıdaki sorgunun çalışması gerektiği izlenimini verir, ancak aslında col1 ve col2'de birlikte benzersiz bir dizin gerektireceği için değildir. Bununla birlikte, böyle bir endeks, OP'nin gereksinimlerinden biri olan col1 ve col2'nin ayrı ayrı benzersiz olacağını garanti etmez.

INSERT INTO dupes values(3,2,'c') 
ON CONFLICT (col1,col2) DO UPDATE SET col3 = 'c', col2 = 2

Bu sorguyu Q2 olarak adlandıralım (bu bir sözdizimi hatasıyla başarısız olur)

Neden?

Postgresql bu şekilde davranır çünkü ikinci sütunda bir çakışma olduğunda ne olması gerektiği iyi tanımlanmamıştır. Bir dizi olasılık var. Örneğin, yukarıdaki Q1 sorgusunda, col1üzerinde bir çakışma olduğunda postgresql güncellenmeli col2mi? Ama ya bu başka bir çatışmaya yol açarsa col1? postgresql'in bunu nasıl ele alması bekleniyor?

Bir çözüm

Bir çözüm, ON CONFLICT'i eski moda UPSERT ile birleştirmektir .

CREATE OR REPLACE FUNCTION merge_db(key1 INT, key2 INT, data TEXT) RETURNS VOID AS
$$
BEGIN
    LOOP
        -- first try to update the key
        UPDATE dupes SET col3 = data WHERE col1 = key1 and col2 = key2;
        IF found THEN
            RETURN;
        END IF;

        -- not there, so try to insert the key
        -- if someone else inserts the same key concurrently, or key2
        -- already exists in col2,
        -- we could get a unique-key failure
        BEGIN
            INSERT INTO dupes VALUES (key1, key2, data) ON CONFLICT (col1) DO UPDATE SET col3 = data;
            RETURN;
        EXCEPTION WHEN unique_violation THEN
            BEGIN
                INSERT INTO dupes VALUES (key1, key2, data) ON CONFLICT (col2) DO UPDATE SET col3 = data;
                RETURN;
            EXCEPTION WHEN unique_violation THEN
                -- Do nothing, and loop to try the UPDATE again.
            END;
        END;
    END LOOP;
END;
$$
LANGUAGE plpgsql;

Sütunları tam olarak istediğiniz şekilde güncellemesi için bu depolanan işlevin mantığını değiştirmeniz gerekir. Gibi çağır

SELECT merge_db(3,2,'c');
SELECT merge_db(1,2,'d');

3
Bu yol çalışır, ancak gerekenden biraz daha fazla çalışma / mantık, gerçekten yapmanız gereken tek şey iki sütun üzerinde benzersiz bir kısıt oluşturmaktır. Aşağıdaki cevabıma bakın.
Jubair

aynı anda birden fazla DEĞER kümesi ekliyorsam da merge_db çözümünü kullanabilir miyim?
daniyel

@daniyel depolanan işlevi yeniden yazmanız gerekecek
e4c5

3
Eski moda yükseltmeyi kullanmayı önermenin ne kadar yararlı olduğu benim için net değil - bu soruya "postgres upsert 9.5" için iyi bir şekilde atıfta bulunuluyor ve tüm constraint_names seçenekleriyle nasıl kullanılacağını açıklayarak daha iyi olabilir.
Pak

3
@Pak Soruyu net okumadığın için sana açık değil. Operasyon, bu alanlarda bileşik bir anahtar aramıyor. Diğer yanıt, bileşik anahtarlar için geçerlidir
e4c5

65

ON CONFLICTçakışma tespitini yapmak için benzersiz bir indeks * gerektirir. Yani her iki sütunda da benzersiz bir dizin oluşturmanız yeterlidir:

t=# create table t (id integer, a text, b text);
CREATE TABLE
t=# create unique index idx_t_id_a on t (id, a);
CREATE INDEX
t=# insert into t values (1, 'a', 'foo');
INSERT 0 1
t=# insert into t values (1, 'a', 'bar') on conflict (id, a) do update set b = 'bar';
INSERT 0 1
t=# select * from t;
 id | a |  b  
----+---+-----
  1 | a | bar

* Benzersiz dizinlere ek olarak, dışlama sınırlamalarını da kullanabilirsiniz . Bunlar benzersiz kısıtlamalardan biraz daha geneldir. Tablonuzda idve valid_time(ve valid_timea tsrange) için sütunlar olduğunu ve yinelenen idzaman dilimlerine izin vermek istemediğinizi , ancak çakışan dönemler için değil. Benzersiz bir kısıtlama size yardımcı olmaz, ancak bir dışlama kısıtlamasıyla "yeni kayıtları ideskiye eşitse idve aynı zamanda valid_timeüst üste biniyorsa hariç tut" diyebilirsiniz valid_time.


4
Bunun yarattığı şey, birlikte benzersiz bir dizindir, benzersiz bir dizin oluştur idx_t_id_a on t (id, a); Elbette OP, iki sütunun ayrı ayrı mı yoksa birlikte mi benzersiz olduğunu açıkça belirtmiyor.
e4c5

Neden postgres bazen dizinin adını taşıyan sütun olmadığını söylüyor ve kullanılamıyor ON CONFLICT?
Pak

@Pak, kullandığınız belirli komutla ve aldığınız hata mesajıyla kendi sorunuzu yazmanız gerektiği gibi geliyor.
Paul A Jungwirth

@PaulAJungwirth Bilmiyorum, cevabınız yerinde - on conflictkomut için bir kısıtlama olarak benzersiz bir dizin . Hata sadece "my_index_name sütunum mevcut değil" dir.
Pak

Bunu yine de OP'nin sorduğu gibi her sütunda ayrı bir benzersiz kısıtlama ile denedim ve işe yaramadı. Beklediğimden değil ama umuyordum.
sudo

6

Günümüzde imkansız (görünüyor). ON CONFLICT Sözdiziminin son sürümü cümlenin tekrarlanmasına izin vermez , ne de CTE ile mümkündür: INSERT'i ON CONFLICT'ten daha fazla çatışma hedefi eklemek için bozmak mümkün değildir.


3

Postgres 9.5 kullanıyorsanız, HARİÇ TUTULAN alanı kullanabilirsiniz.

PostgreSQL 9.5'teki yeniliklerden alınan örnek :

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

2
  1. Bir kısıt oluşturun (örneğin yabancı dizin).

VEYA / VE

  1. Mevcut kısıtlamalara bakın (psq cinsinden \ d).
  2. INSERT yan tümcesinde ON CONSTRAINT (kısıt_adı) kullanın.

1

Voyvoda doğru fikri aldı.

Öncelikle sütunlarda benzersiz bir tablo kısıtlaması oluşturmanız gerekir. col1, col2 Bunu yaptıktan sonra aşağıdakileri yapabilirsiniz:

INSERT INTO dupes values(3,2,'c') 
ON CONFLICT ON CONSTRAINT dupes_pkey 
DO UPDATE SET col3 = 'c', col2 = 2

5
Üzgünüm ama soruyu yanlış anladınız. OP, birlikte benzersiz bir kısıtlama istemiyor.
e4c5

1

Biraz zor ama bunu, col1 ve col2'deki iki değeri yeni bir sütun olan col3'e (ikisinin bir indeksi gibi) birleştirerek ve bununla karşılaştırarak çözdüm. Bu yalnızca col1 ve col2'nin HER İKİSİ ile eşleşmesine ihtiyacınız varsa çalışır.

INSERT INTO table
...
ON CONFLICT ( col3 ) 
DO UPDATE 
SET 
-- update needed columns here

Col3 = col1 ve col2'den değerlerin birleştirilmesi.


3
bu iki sütun için benzersiz bir dizin oluşturabilir ve bu kısıtlamayı içinde verebilirsiniz on conflict.
Kishore Relangi

0

Tipik olarak (sanıyorum) on conflict, eklediğiniz şey için ilgili olan tek ve tek kısıtlamayı belirten bir ifade oluşturabilirsiniz .

Çünkü tipik olarak, bir seferde yalnızca bir kısıt "alakalı" olandır. (Çoksa, bir şeyin tuhaf / tuhaf tasarlanmış olup olmadığını merak ediyorum, hmm.)

Örnek:
(Lisans: CC0 değil , yalnızca CC-By)

// there're these unique constraints:
//   unique (site_id, people_id, page_id)
//   unique (site_id, people_id, pages_in_whole_site)
//   unique (site_id, people_id, pages_in_category_id)
// and only *one* of page-id, category-id, whole-site-true/false
// can be specified. So only one constraint is "active", at a time.

val thingColumnName = thingColumnName(notfificationPreference)

val insertStatement = s"""
  insert into page_notf_prefs (
    site_id,
    people_id,
    notf_level,
    page_id,
    pages_in_whole_site,
    pages_in_category_id)
  values (?, ?, ?, ?, ?, ?)
  -- There can be only one on-conflict clause.
  on conflict (site_id, people_id, $thingColumnName)   <—— look
  do update set
    notf_level = excluded.notf_level
  """

val values = List(
  siteId.asAnyRef,
  notfPref.peopleId.asAnyRef,
  notfPref.notfLevel.toInt.asAnyRef,
  // Only one of these is non-null:
  notfPref.pageId.orNullVarchar,
  if (notfPref.wholeSite) true.asAnyRef else NullBoolean,
  notfPref.pagesInCategoryId.orNullInt)

runUpdateSingleRow(insertStatement, values)

Ve:

private def thingColumnName(notfPref: PageNotfPref): String =
  if (notfPref.pageId.isDefined)
    "page_id"
  else if (notfPref.pagesInCategoryId.isDefined)
    "pages_in_category_id"
  else if (notfPref.wholeSite)
    "pages_in_whole_site"
  else
    die("TyE2ABK057")

on conflictFıkra dinamik ben yapmaya çalışıyorum şeye bağlı olarak oluşturulur. Bir sayfa için bir bildirim tercihi eklersem, site_id, people_id, page_idkısıtlamada benzersiz bir çakışma olabilir . Ve eğer bir kategori için bildirim tercihlerini yapılandırıyorsam - bunun yerine ihlal edilebilecek kısıtlamanın olduğunu biliyorum site_id, people_id, category_id.

Yani ben yapabilirim ve oldukça büyük olasılıkla, sizin durumunuzda doğru on conflict (... columns )olanı üretebilirim , çünkü ne yapmak istediğimi biliyorum ve sonra birçok benzersiz kısıtlamadan hangisinin ihlal edilebileceğini biliyorum.


-4

ON CONFLICT çok beceriksiz bir çözüm, koş

UPDATE dupes SET key1=$1, key2=$2 where key3=$3    
if rowcount > 0    
  INSERT dupes (key1, key2, key3) values ($1,$2,$3);

Oracle, Postgres ve diğer tüm veritabanı üzerinde çalışır


Atomik değildir, bu nedenle aynı anda birden fazla bağlantı olması durumunda başarısız olabilir ve yanlış sonuçlar verebilir.
Bogdan Mart
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.