ओरेकल: यूपीएसईआरटी को कैसे अपडेट करें (एक तालिका में डालें या डालें?)


293

UPSERT ऑपरेशन किसी तालिका में एक पंक्ति को अद्यतन या सम्मिलित करता है, यह निर्भर करता है कि तालिका में पहले से ही एक पंक्ति है जो डेटा से मेल खाती है:

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

चूंकि ओरेकल में एक विशिष्ट यूपीएसईआरटी कथन नहीं है, इसलिए ऐसा करने का सबसे अच्छा तरीका क्या है?

जवाबों:


60

MERGE का एक विकल्प ("पुराने ढंग का"):

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: वास्तव में? एक स्पष्टीकरण सहायक होगा।
टोनी एंड्रयूज

15
मुद्दा यह है कि आपके पास डालने और अद्यतन के बीच एक विंडो है जहां एक और प्रक्रिया सफलतापूर्वक एक आग को नष्ट कर सकती है। हालाँकि मैंने इस पैटर्न का उपयोग एक मेज पर किया था, जिसने कभी भी इसके खिलाफ निकाल दिया गया है।
छोटकी

2
ठीक है मुझे स्वीकार है। पता नहीं क्यों यह मेरे लिए स्पष्ट नहीं था।
टोनी एंड्रयूज

4
मैं छोटकी से असहमत हूं। "लॉक अवधि: एक लेन-देन के भीतर बयानों द्वारा अधिग्रहित सभी ताले लेन-देन की अवधि के लिए आयोजित किए जाते हैं, जो गंदे रीड्स, खोए हुए अपडेट और विनाशकारी डीडीएल संचालन सहित समवर्ती लेनदेन से विनाशकारी हस्तक्षेप को रोकते हैं।"
सूस

5
@yohannc: मुझे लगता है कि बिंदु यह है कि हमने केवल एक पंक्ति सम्मिलित करने की कोशिश करके और असफल होकर किसी भी ताले को हासिल नहीं किया है।
टोनी एंड्रयूज

211

मर्ज बयान दो तालिकाओं के बीच डेटा विलीन हो जाती है। DUAL के उपयोग से हम इस कमांड का उपयोग कर सकते हैं। ध्यान दें कि यह समवर्ती पहुंच के खिलाफ संरक्षित नहीं है।

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
जाहिरा तौर पर "बयान में विलय" परमाणु नहीं है। समवर्ती रूप से उपयोग किए जाने पर इसका परिणाम "ORA-0001: अद्वितीय बाधा" हो सकता है। एक मैच के अस्तित्व की जांच और एक नए रिकॉर्ड की प्रविष्टि एक ताला द्वारा संरक्षित नहीं है, इसलिए एक दौड़ की स्थिति है। मज़बूती से ऐसा करने के लिए, आपको इस अपवाद को पकड़ने की आवश्यकता है और या तो मर्ज को फिर से चलाएं या इसके बजाय एक सरल अपडेट करें। ओरेकल 10 में, आप "लॉग त्रुटियों" क्लॉज का उपयोग कर सकते हैं ताकि यह बाकी पंक्तियों के साथ जारी रहे जब एक त्रुटि होती है और केवल रोके जाने के बजाय आपत्तिजनक पंक्ति को किसी अन्य तालिका में लॉग इन करें।
टिम सिल्वेस्टर

1
नमस्ते, मैंने अपनी क्वेरी में समान क्वेरी पैटर्न का उपयोग करने की कोशिश की, लेकिन किसी तरह मेरी क्वेरी डुप्लिकेट पंक्तियों को सम्मिलित कर रही है। मुझे DUAL तालिका के बारे में अधिक जानकारी नहीं मिल रही है। क्या कोई मुझे बता सकता है कि मुझे डीयूएल की जानकारी कहां मिल सकती है और मर्ज सिंटैक्स के बारे में भी?
शेखर


7
@TimSylvester - ऑरेकल लेन-देन का उपयोग करता है, इसलिए लेन-देन की शुरुआत में डेटा के स्नैपशॉट की गारंटी देता है, यह लेन-देन के दौरान संगत है जो कि इसके भीतर किए गए किसी भी परिवर्तन को सहेजता है। डेटाबेस में समवर्ती कॉल पूर्ववत स्टैक का उपयोग करता है; इसलिए ओरेकल अंतिम राज्य का प्रबंधन करेगा जब आदेश के आधार पर समवर्ती लेनदेन शुरू / पूरा हो जाएगा। इसलिए, यदि रेसक्यूंट चेक एक ही SQL कोड में कितने समवर्ती कॉल किए जाते हैं, तो डालने से पहले आपको कभी भी रेस की स्थिति नहीं होगी। सबसे खराब स्थिति, आपको बहुत सारे विवाद हो सकते हैं और ओरेकल को अंतिम स्थिति तक पहुंचने में बहुत समय लगेगा।
नव

2
@RandyMagruder क्या यह मामला ऐसा है कि 2015 में और हम अभी भी ओरेकल में मज़बूती से एक बड़ा काम नहीं कर सकते हैं! क्या आप समवर्ती सुरक्षित समाधान के बारे में जानते हैं?
dan b

105

ऊपर दिया गया दोहरी उदाहरण जो पीएल / एसक्यूएल में है वह बहुत बड़ा था क्योंकि मैं ऐसा ही कुछ करना चाहता था, लेकिन मैं इसे ग्राहक पक्ष चाहता था ... इसलिए यहां एसक्यूएल है जो मैं कुछ सी # से इसी तरह का सीधा बयान भेजता था।

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

हालाँकि C # दृष्टिकोण से यह अपडेट करने की तुलना में धीमा होने के लिए प्रदान करता है और यह देखने के लिए कि क्या प्रभावित पंक्तियाँ 0 थीं और अगर थी तो सम्मिलित कर रही थीं।


10
मैं इस पैटर्न को फिर से देखने के लिए यहां वापस आया हूं। जब समवर्ती आवेषण का प्रयास किया जाता है तो यह चुपचाप विफल हो जाता है। एक इंसर्ट प्रभावी होता है, दूसरा मर्ज न तो इन्सर्ट करता है और न ही अपडेट। हालांकि, दो अलग-अलग कथन करने का तेज़ तरीका सुरक्षित है।
साइनेसियो

3
मेरे जैसे oralcle newbies क्या है यह पूछ सकते हैं दोहरी : तालिका में देखते stackoverflow.com/q/73751/808698
हाजो Thelen

5
बहुत बुरा है कि इस पैटर्न के साथ हमें दो बार डेटा (जॉन, स्मिथ ...) लिखना होगा । इस मामले में, मैं कुछ भी नहीं का उपयोग कर जीतता हूं , और मैं तब बहुत सरल का उपयोग करना पसंद करता हूं । MERGEDELETEINSERT
निकोलस बारबुलेसको


@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

अपवाद जाँच के बिना एक और विकल्प:

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;

आपका प्रदान किया गया समाधान मेरे लिए काम नहीं करता है। क्या% rowcount केवल स्पष्ट कर्सर के साथ काम करता है?
सिनेसियो ३१'११

क्या होगा यदि अद्यतन 0 पंक्तियों को संशोधित कर दिया गया क्योंकि रिकॉर्ड पहले से ही था और मान समान थे?
एड्रियानो वरोली पियाज़ा

10
@ Adriano: sql% rowcount अभी भी> 0 पर लौटेगी, यदि WHERE क्लॉज किसी भी पंक्तियों से मेल खाता है, भले ही अपडेट वास्तव में उन पंक्तियों पर कोई डेटा न बदले।
टोनी एंड्रयूज

काम नहीं करता है: PLS-00207: पहचानकर्ता 'COUNT', जो निहित कर्सर SQL पर लागू होता है, एक कानूनी कर्सर विशेषता नहीं है
पेट्रिक बेक

सिंटैक्स त्रुटियाँ यहाँ :(
ilmirons

27
  1. अगर मौजूद नहीं है तो डालें
  2. अपडेट करें:
    
INSERT में mytable (आईडी 1, टी 1) 
  11 का चयन करें, 'X1' DUAL से 
  जहां नहीं है (Mytble से id1 का चयन करें जहां id1 = 11); 

अद्यतन mytable सेट T1 = 'X1' कहाँ id1 = 11;

26

टिम सिल्वेस्टर की टिप्पणी के अनुसार, अब तक दिए गए जवाबों में से कोई भी समवर्ती पहुंच के चेहरे पर सुरक्षित नहीं है , और दौड़ के मामले में अपवादों को बढ़ाएगा। इसे ठीक करने के लिए, इंसर्ट / अपडेट कॉम्बो को किसी प्रकार के लूप स्टेटमेंट में लपेटा जाना चाहिए, ताकि अपवाद की स्थिति में पूरी चीज वापस आ जाए।

एक उदाहरण के रूप में, यहाँ कैसे Grommit कोड को लूप में लपेटा जा सकता है, जब इसे समवर्ती रूप से सुरक्षित किया जा सके:

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; 

NB लेनदेन मोड में SERIALIZABLE, जिसे मैं btw की सलाह नहीं देता, आप ORA-08177 में चला सकते हैं: इसके बजाय इस लेनदेन के लिए पहुँच को क्रमबद्ध नहीं कर सकते


3
अति उत्कृष्ट! अंत में, एक समवर्ती पहुँच सुरक्षित उत्तर देता है। किसी क्लाइंट (जैसे जावा क्लाइंट से) से ऐसे निर्माण का उपयोग करने का कोई तरीका?
सेबियन

1
आप एक संग्रहीत खरीद कॉल करने के लिए नहीं मतलब है? ठीक है, उस स्थिति में आप केवल विशिष्ट जावा अपवादों को पकड़ सकते हैं और जावा लूप में पुन: प्रयास कर सकते हैं। यह Oracle के SQL की तुलना में जावा में बहुत अधिक सुविधाजनक है।
यूजीन बेरेसोवस्की

मुझे क्षमा करें: मैं पर्याप्त विशिष्ट नहीं था। लेकिन आपने सही तरीका समझा। मैंने आपके जैसा कहने के लिए इस्तीफा दे दिया। लेकिन मैं 100% संतुष्ट नहीं हूं क्योंकि यह अधिक SQL क्वेरी, अधिक क्लाइंट / सर्वर राउंडट्रिप्स उत्पन्न करता है। यह एक अच्छा समाधान प्रदर्शन-वार नहीं है। लेकिन मेरा लक्ष्य मेरी परियोजना के जावा डेवलपर्स को किसी भी तालिका में उखाड़ने के लिए मेरी विधि का उपयोग करने देना है (मैं प्रति तालिका एक PLSQL संग्रहीत प्रक्रिया नहीं बना सकता हूं, या प्रतिप्रकार प्रति एक प्रक्रिया)।
सेबियन

@ एसबिएन मैं सहमत हूं, यह अच्छा होगा कि इसे एसक्यूएल दायरे में समझाया जाए, और मुझे लगता है कि आप इसे कर सकते हैं। मैं सिर्फ आपके लिए यह पता लगाने के लिए स्वेच्छा से नहीं हूं ... :) प्लस, वास्तव में ये अपवाद एक नीला चाँद में एक बार से भी कम होगा, इसलिए आपको 99.9% मामलों में प्रदर्शन पर प्रभाव नहीं देखना चाहिए। सिवाय कोर्स की लोड टेस्टिंग करते समय ...
यूजीन बेर्सोव्स्की

24

मुझे ग्रोमिट उत्तर पसंद है, सिवाय इसके कि इसे मूल्‍य मान की आवश्‍यकता है। मुझे इसका समाधान मिला जहाँ यह एक बार दिखाई दे सकता है: 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
क्या आपका मतलब था INSERT (B.CILT, B.SAYFA, B.KUTUK, B.MERNIS_NO) VALUES (E.CILT, E.SAYFA, E.KUTUK, E.MERNIS_NO); ?
मत्ती

ज़रूर। धन्यवाद। फिक्स्ड।
हबबिटस

शुक्र है कि आपने अपना जवाब संपादित किया! :) मेरा संपादन
Matteo

9

सुझाव देने वाले दो समाधानों के बारे में एक नोट:

1) डालें, अगर अपवाद है तो अपडेट करें,

या

2) अद्यतन, यदि sql% rowcount = 0 तो सम्मिलित करें

पहले डालने या अपडेट करने का प्रश्न भी आवेदन पर निर्भर है। क्या आप अधिक आवेषण या अधिक अपडेट की उम्मीद कर रहे हैं? जो सबसे सफल होने की संभावना है, उसे पहले जाना चाहिए।

यदि आप गलत उठाते हैं तो आपको अनावश्यक इंडेक्स रीड्स का एक गुच्छा मिलेगा। बहुत बड़ी बात नहीं है, लेकिन फिर भी कुछ विचार करने के लिए।


sql% नोटफाउंड मेरी व्यक्तिगत प्राथमिकता है
आर्टूरो हर्नांडेज़

8

मैं वर्षों के लिए पहले कोड नमूने का उपयोग कर रहा हूं। नोट करने के बजाय नोटिस न करें।

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;

नीचे दिया गया कोड संभवतः नया और बेहतर कोड है

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)

पहले उदाहरण में अपडेट एक इंडेक्स लुकअप करता है। यह सही पंक्ति को अद्यतन करने के लिए है। ओरेकल एक निहित कर्सर खोलता है, और हम इसका उपयोग इसी प्रविष्टि को लपेटने के लिए करते हैं ताकि हम जान सकें कि प्रविष्टि केवल तब होगी जब कुंजी मौजूद नहीं होगी। लेकिन इंसर्ट एक स्वतंत्र कमांड है और इसे दूसरा लुकअप करना होता है। मुझे मर्ज कमांड के आंतरिक कामकाज का पता नहीं है लेकिन चूंकि कमांड एक एकल इकाई है, इसलिए ओरेकल एकल इंडेक्स लुकअप के साथ सही इंसर्ट या अपडेट निष्पादित कर सकता है।

मुझे लगता है कि जब आप कुछ प्रसंस्करण करते हैं तो इसका मतलब बेहतर होता है कि कुछ तालिकाओं से डेटा लेना और एक तालिका को अपडेट करना, संभवतः पंक्तियों को सम्मिलित करना या हटाना। लेकिन एकल पंक्ति मामले के लिए, आप पहले मामले पर विचार कर सकते हैं क्योंकि वाक्य रचना अधिक सामान्य है।


0

MERGE के साथ एक तालिका को दूसरे में ऊपर रखने के लिए कॉपी और पेस्ट उदाहरण:

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

परिणाम:

  1. बी ४ ५
  2. ग ३ ३
  3. एक 1 1

-3

इसे इस्तेमाल करे,

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

से http://www.praetoriate.com/oracle_tips_upserts.htm :

"Oracle9i में, एक UPSERT इस कार्य को एक बयान में पूरा कर सकता है:"

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 ठेठ डॉन बर्ल्सन cr @ p मुझे डर है - यह एक मेज या किसी अन्य में एक सम्मिलित है, यहां कोई "अपटाउन" नहीं है!
टोनी एंड्रयूज
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.