Oracle: UPSERT nasıl (güncelleme veya tabloya ekleme?)


293

UPSERT işlemi, tabloda zaten verilerle eşleşen bir satır olmasına bağlı olarak tabloya bir satır ekler veya bir satır ekler:

if table t has a row exists that has key X:
    update t set mystuff... where mykey=X
else
    insert into t mystuff...

Oracle'ın belirli bir UPSERT ifadesi olmadığından, bunu yapmanın en iyi yolu nedir?

Yanıtlar:


60

MERGE'e bir alternatif ("eski moda yol"):

begin
   insert into t (mykey, mystuff) 
      values ('X', 123);
exception
   when dup_val_on_index then
      update t 
      set    mystuff = 123 
      where  mykey = 'X';
end;   

3
@chotchki: gerçekten mi? Bir açıklama yardımcı olacaktır.
Tony Andrews

15
Sorun, ekleme ve güncelleme arasında başka bir işlemin başarıyla silme işlemini başlatabileceği bir pencereniz olması. Ancak bu kalıbı asla silmediği bir tablo üzerinde kullandım.
chotchki

2
Tamam, katılıyorum. Benim için neden açık olmadığını bilmiyorum.
Tony Andrews

4
Chotchki'ye katılmıyorum. "Kilitleme Süresi: Bir işlemdeki ifadeler tarafından alınan tüm kilitler, işlem süresince tutulur ve kirli okumalar, kayıp güncellemeler ve yıkıcı DDL işlemlerinin eşzamanlı işlemlerden kaynaklanan yıkıcı paraziti önler."
Sos

5
@yohannc: Bence sadece bir satır eklemeyi denemek ve başarısız bir kilit elde edemiyorum.
Tony Andrews

211

BİRLEŞTİRME deyim iki tablo arasında veri birleştirir. DUAL kullanılması, bu komutu kullanmamızı sağlar. Bunun eşzamanlı erişime karşı korumalı olmadığını unutmayın.

create or replace
procedure ups(xa number)
as
begin
    merge into mergetest m using dual on (a = xa)
         when not matched then insert (a,b) values (xa,1)
             when matched then update set b = b+1;
end ups;
/
drop table mergetest;
create table mergetest(a number, b number);
call ups(10);
call ups(10);
call ups(20);
select * from mergetest;

A                      B
---------------------- ----------------------
10                     2
20                     1

57
Görünüşe göre "birleştirme" deyimi atomik değildir. Aynı anda kullanıldığında "ORA-0001: benzersiz kısıtlama" ile sonuçlanabilir. Bir eşleşme olup olmadığını kontrol etme ve yeni bir kayıt ekleme kilidi tarafından korunmaz, bu nedenle bir yarış durumu vardır. Bunu güvenilir bir şekilde yapmak için, bu istisnayı yakalamanız ve birleştirmeyi yeniden çalıştırmanız veya bunun yerine basit bir güncelleme yapmanız gerekir. Oracle 10'da, bir hata oluştuğunda satırların geri kalanıyla devam etmesini sağlamak için "günlük hataları" yan tümcesini kullanabilir ve rahatsız edici satırı yalnızca durmak yerine başka bir tabloya kaydedebilirsiniz.
Tim Sylvester

1
Merhaba, benim sorguda aynı sorgu desenini kullanmaya çalıştım ama bir şekilde benim sorgu yinelenen satırlar ekliyor. DUAL tablosu hakkında daha fazla bilgi bulamıyorum. Herkes bana DUAL hakkında bilgi nereden ve ayrıca birleştirme sözdizimi hakkında bilgi alabilir miyim?
Shekhar

5
@Shekhar Dual, tek sıralı ve sütunlu adp-gmbh.ch/ora/misc/dual.html
YogoZuno

7
@TimSylvester - Oracle işlemleri kullanır, bu nedenle bir işlemin başlangıcında verilerin anlık görüntüsünün işlem boyunca tutarlı olmasını garanti eder. Veritabanına eşzamanlı çağrılar geri alma yığınını kullanır; böylece Oracle, eşzamanlı işlemlerin ne zaman başladığını / tamamlandığını temel alarak son durumu yönetir. Dolayısıyla, aynı SQL koduna kaç eşzamanlı çağrı yapıldığına bakılmaksızın ekleme işleminden önce bir kısıtlama kontrolü yapılırsa hiçbir zaman bir yarış durumunuz olmaz. En kötü durumda, çok çekişme yaşayabilirsiniz ve Oracle'ın nihai duruma ulaşması daha uzun sürecektir.
Neo

2
@RandyMagruder 2015 ve hala Oracle'da güvenilir bir şekilde yukarı çıkamıyoruz! Eşzamanlı güvenli bir çözüm biliyor musunuz?
dan b

105

PL / SQL olan yukarıdaki çift örnek ben benzer bir şey yapmak istedim çünkü büyük oldu, ama istemci tarafı istedim ... işte burada bazı C # doğrudan benzer bir ifade göndermek için kullanılan SQL

MERGE INTO Employee USING dual ON ( "id"=2097153 )
WHEN MATCHED THEN UPDATE SET "last"="smith" , "name"="john"
WHEN NOT MATCHED THEN INSERT ("id","last","name") 
    VALUES ( 2097153,"smith", "john" )

Bununla birlikte, bir C # perspektifinden, bu güncelleme yapmaktan ve etkilenen satırların 0 olup olmadığını görmek ve eğer eklenti yapmaktan daha yavaş olmasını sağlar.


10
Bu kalıbı tekrar kontrol etmek için buraya geldim. Eşzamanlı uçlar denendiğinde sessizce başarısız olur. Bir ek yürürlüğe girer, ikinci birleştirme ne ekler ne de güncelleştirilir. Bununla birlikte, iki ayrı ifade yapmanın daha hızlı yaklaşımı güvenlidir.
Synesso

3
benim gibi oralcle yeni başlayanlar bu ikili tablonun ne olduğunu sorabilir : stackoverflow.com/q/73751/808698
Hajo Thelen

5
Bu kalıpla iki kez veri yazmamız gerektiği çok kötü (John, Smith ...). Bu durumda, kullanarak hiçbir şey kazanmam ve o zaman çok daha basit kullanmayı tercih ederim . MERGEDELETEINSERT
Nicolas Barbulesco

@NicolasBarbulesco bu cevabın verileri iki kez yazmasına gerek yok: stackoverflow.com/a/4015315/8307814
whyer

@NicolasBarbulescoMERGE INTO mytable d USING (SELECT 1 id, 'x' name from dual) s ON (d.id = s.id) WHEN MATCHED THEN UPDATE SET d.name = s.name WHEN NOT MATCHED THEN INSERT (id, name) VALUES (s.id, s.name);
whyer

46

İstisna kontrolü olmayan başka bir alternatif:

UPDATE tablename
    SET val1 = in_val1,
        val2 = in_val2
    WHERE val3 = in_val3;

IF ( sql%rowcount = 0 )
    THEN
    INSERT INTO tablename
        VALUES (in_val1, in_val2, in_val3);
END IF;

Sağladığınız çözüm benim için çalışmıyor. % Rowcount yalnızca açık imleçlerle çalışır mı?
Synesso

Güncelleme, kayıt zaten orada olduğu ve değerler aynı olduğu için değiştirilmiş 0 satır döndürürse ne olur?
Adriano Varoli Piazza

10
@Adriano: sql% rowcount, WHERE deyimi herhangi bir satırla eşleşirse, güncelleştirme bu satırlardaki verileri gerçekten değiştirmese bile> 0 değerini döndürür.
Tony Andrews

Çalışmıyor: PLS-00207: örtülü imleç SQL'e uygulanan 'COUNT' tanımlayıcısı yasal bir imleç özelliği değil
Patrik Beck

Sözdizimi Hataları burada :(
ilmirons

27
  1. yoksa ekle
  2. Güncelleme:
    
Değişken içine ekle (id1, t1) 
  DUAL'DEN 11, 'x1' SEÇ 
  NEREDE OLMAMAKTADIR (Id1'i mytble'DAN SEÇİN id1 = 11); 

GÜNCELLEME mytable SET t1 = 'x1' NEREDE id1 = 11;

26

Şimdiye kadar verilen cevapların hiçbiri eşzamanlı erişim karşısında güvenli değildir Tim Sylvester'in yorumunda belirtildiği gibi ve yarışlarda istisnalar doğuramaz. Bunu düzeltmek için, ekleme / güncelleme birleşiminin bir tür döngü deyimine sarılması gerekir, böylece bir istisna durumunda her şey yeniden denenir.

Örnek olarak, Grommit'in kodu aynı anda çalıştırıldığında güvenli hale getirmek için bir döngüye nasıl sarılabilir:

PROCEDURE MyProc (
 ...
) IS
BEGIN
 LOOP
  BEGIN
    MERGE INTO Employee USING dual ON ( "id"=2097153 )
      WHEN MATCHED THEN UPDATE SET "last"="smith" , "name"="john"
      WHEN NOT MATCHED THEN INSERT ("id","last","name") 
        VALUES ( 2097153,"smith", "john" );
    EXIT; -- success? -> exit loop
  EXCEPTION
    WHEN NO_DATA_FOUND THEN -- the entry was concurrently deleted
      NULL; -- exception? -> no op, i.e. continue looping
    WHEN DUP_VAL_ON_INDEX THEN -- an entry was concurrently inserted
      NULL; -- exception? -> no op, i.e. continue looping
  END;
 END LOOP;
END; 

Not : BTW'yi önermediğim işlem modunda SERIALIZABLE, ORA-08177 ile karşılaşabilirsiniz: bunun yerine bu işlem istisnaları için erişimi serileştiremezsiniz .


3
Mükemmel! Son olarak, eşzamanlı olarak güvenli cevaba erişir. Bir istemciden (örneğin bir Java istemcisinden) böyle bir yapıyı kullanmanın herhangi bir yolu var mı?
Sebien

1
Kayıtlı bir proc çağırmak zorunda değil mi? Bu durumda, yalnızca belirli Java istisnalarını yakalayabilir ve bir Java döngüsünde tekrar deneyebilirsiniz. Java'da Oracle'ın SQL'sinden çok daha kullanışlı bir cehennem.
Eugene Beresovsky

Üzgünüm: Yeterince spesifik değildim. Ama sen doğru yolu anladın. Dediğin gibi yapmak için istifa ettim. Ancak daha fazla SQL sorgusu, daha fazla istemci / sunucu gidiş dönüş ürettiği için% 100 memnun değilim. Performans açısından iyi bir çözüm değildir. Ancak amacım, projemin Java geliştiricilerinin herhangi bir tabloya eklemek için yöntemimi kullanmasına izin vermektir (tablo başına bir PLSQL saklı yordamı veya upert türü başına bir yordam oluşturamıyorum).
Sebien

@Sebien Kabul ediyorum, SQL aleminde kapsüllenmiş olması daha hoş olurdu ve bunu yapabileceğinizi düşünüyorum. Sadece sizin için anlamaya gönüllü değilim ... :) Artı, gerçekte bu istisnalar muhtemelen mavi ayda bir kereden daha az gerçekleşecek, bu yüzden vakaların% 99,9'unda performans üzerinde bir etki görmemelisiniz. Tabii ki yük testi yaparken hariç ...
Eugene Beresovsky

24

Grommit yanıtı istiyorum, ancak dupe değerleri gerektiriyor. Bir kez görülebileceği bir çözüm buldum: http://forums.devshed.com/showpost.php?p=1182653&postcount=2

MERGE INTO KBS.NUFUS_MUHTARLIK B
USING (
    SELECT '028-01' CILT, '25' SAYFA, '6' KUTUK, '46603404838' MERNIS_NO
    FROM DUAL
) E
ON (B.MERNIS_NO = E.MERNIS_NO)
WHEN MATCHED THEN
    UPDATE SET B.CILT = E.CILT, B.SAYFA = E.SAYFA, B.KUTUK = E.KUTUK
WHEN NOT MATCHED THEN
    INSERT (  CILT,   SAYFA,   KUTUK,   MERNIS_NO)
    VALUES (E.CILT, E.SAYFA, E.KUTUK, E.MERNIS_NO); 

2
Bunu mu demek istediniz INSERT (B.CILT, B.SAYFA, B.KUTUK, B.MERNIS_NO) VALUES (E.CILT, E.SAYFA, E.KUTUK, E.MERNIS_NO); ?
Matteo

Elbette. Teşekkürler. Sabit.
Hubbitus

Neyse ki cevabınızı düzenlediniz! :) düzenlemem ne yazık ki reddetti stackoverflow.com/review/suggested-edits/7555674
Matteo

9

Şu iki çözümle ilgili bir not:

1) İsterseniz istisna sonra güncelleyin,

veya

2) Güncelleme, sql% rowcount = 0 ise,

Önce ekleme veya güncelleme sorusu da uygulamaya bağlıdır. Daha fazla ek veya daha fazla güncelleme mi bekliyorsunuz? Başarılı olma olasılığı en yüksek olanı önce gitmelidir.

Yanlış olanı seçerseniz, bir sürü gereksiz dizin okuması elde edersiniz. Büyük bir anlaşma değil, yine de dikkate alınması gereken bir şey.


sql% notfound benim kişisel tercihim
Arturo Hernandez

8

Yıllardır ilk kod örneğini kullanıyorum. Dikkat saymak yerine not edildi.

UPDATE tablename SET val1 = in_val1, val2 = in_val2
    WHERE val3 = in_val3;
IF ( sql%notfound ) THEN
    INSERT INTO tablename
        VALUES (in_val1, in_val2, in_val3);
END IF;

Aşağıdaki kod muhtemelen yeni ve geliştirilmiş koddur

MERGE INTO tablename USING dual ON ( val3 = in_val3 )
WHEN MATCHED THEN UPDATE SET val1 = in_val1, val2 = in_val2
WHEN NOT MATCHED THEN INSERT 
    VALUES (in_val1, in_val2, in_val3)

İlk örnekte, güncelleme bir dizin araması yapar. Doğru satırı güncellemek için gerekir. Oracle, örtük bir imleç açar ve karşılık gelen bir eki sarmak için kullanırız, böylece ekin yalnızca anahtar bulunmadığında gerçekleşeceğini biliyoruz. Ancak ekleme bağımsız bir komuttur ve ikinci bir arama yapmak zorundadır. Birleştirme komutunun iç işleyişini bilmiyorum, ancak komut tek bir birim olduğundan, Oracle tek bir dizin aramasıyla doğru ekleme veya güncellemeyi yürütebilirdi.

Bazı tablolardan veri almak ve bir tabloyu güncellemek, muhtemelen satır eklemek veya silmek anlamına gelen yapılacak bazı işlemlerinizde birleştirmenin daha iyi olduğunu düşünüyorum. Ancak tek satırlık durum için, sözdizimi daha yaygın olduğu için ilk durumu düşünebilirsiniz.


0

MERGE ile bir tabloyu diğerine yükseltmek için örnek kopyalayıp yapıştırın:

CREATE GLOBAL TEMPORARY TABLE t1
    (id VARCHAR2(5) ,
     value VARCHAR2(5),
     value2 VARCHAR2(5)
     )
  ON COMMIT DELETE ROWS;

CREATE GLOBAL TEMPORARY TABLE t2
    (id VARCHAR2(5) ,
     value VARCHAR2(5),
     value2 VARCHAR2(5))
  ON COMMIT DELETE ROWS;
ALTER TABLE t2 ADD CONSTRAINT PK_LKP_MIGRATION_INFO PRIMARY KEY (id);

insert into t1 values ('a','1','1');
insert into t1 values ('b','4','5');
insert into t2 values ('b','2','2');
insert into t2 values ('c','3','3');


merge into t2
using t1
on (t1.id = t2.id) 
when matched then 
  update set t2.value = t1.value,
  t2.value2 = t1.value2
when not matched then
  insert (t2.id, t2.value, t2.value2)  
  values(t1.id, t1.value, t1.value2);

select * from t2

Sonuç:

  1. b 4 5
  2. c 3 3
  3. a 1 1

-3

Bunu dene,

insert into b_building_property (
  select
    'AREA_IN_COMMON_USE_DOUBLE','Area in Common Use','DOUBLE', null, 9000, 9
  from dual
)
minus
(
  select * from b_building_property where id = 9
)
;

-6

Gönderen http://www.praetoriate.com/oracle_tips_upserts.htm :

"Oracle9i'de bir UPSERT bu görevi tek bir ifadede gerçekleştirebilir:"

INSERT
FIRST WHEN
   credit_limit >=100000
THEN INTO
   rich_customers
VALUES(cust_id,cust_credit_limit)
   INTO customers
ELSE
   INTO customers SELECT * FROM new_customers;

14
-1 Tipik Don Burleson cr @ p Korkarım - bu bir tabloya veya başka bir tabloya eklenir, burada "yukarı" yoktur!
Tony Andrews
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.