PostgreSQL में डुप्लिकेट अपडेट पर सम्मिलित करें?


644

कई महीने पहले मैंने स्टैक ओवरफ्लो पर एक जवाब से सीखा कि कैसे निम्नलिखित सिंटैक्स का उपयोग करके MySQL में एक बार में कई अपडेट किए जा सकते हैं:

INSERT INTO table (id, field, field2) VALUES (1, A, X), (2, B, Y), (3, C, Z)
ON DUPLICATE KEY UPDATE field=VALUES(Col1), field2=VALUES(Col2);

मैंने अब PostgreSQL पर स्विच कर दिया है और जाहिरा तौर पर यह सही नहीं है। यह सभी सही तालिकाओं की बात कर रहा है, इसलिए मुझे लगता है कि यह अलग-अलग कीवर्ड का उपयोग करने की बात है, लेकिन मुझे यकीन नहीं है कि पोस्टग्रेक्यूएल प्रलेखन में यह कहाँ शामिल है।

स्पष्ट करने के लिए, मैं कई चीजें सम्मिलित करना चाहता हूं और यदि वे पहले से ही उन्हें अपडेट करने के लिए मौजूद हैं।


38
जो कोई भी इस प्रश्न को पाता है, उसे डेपज़ के लेख को पढ़ना चाहिए "उखाड़ फेंकना इतना जटिल क्यों है?" । यह मुद्दे और संभावित समाधानों को बहुत अच्छी तरह से समझाता है।
क्रेग रिंगर

8
Upsert Postgres 9.5 में जोड़ दिया जाएगा: wiki.postgresql.org/wiki/...
tommed

4
@tommed - यह किया गया है: stackoverflow.com/a/34639631/4418
वॉरेन

जवाबों:


515

PostgreSQL के बाद से संस्करण 9.5 है Upsert वाक्य रचना, साथ पर टकराव खंड। निम्नलिखित सिंटैक्स के साथ (MySQL के समान)

INSERT INTO the_table (id, column_1, column_2) 
VALUES (1, 'A', 'X'), (2, 'B', 'Y'), (3, 'C', 'Z')
ON CONFLICT (id) DO UPDATE 
  SET column_1 = excluded.column_1, 
      column_2 = excluded.column_2;

"Upsert" के लिए postgresql के ईमेल समूह अभिलेखागार की खोज करने से ऐसा करने का एक उदाहरण मिलता है जो आप संभवतः मैनुअल में करना चाहते हैं :

उदाहरण 38-2। अद्यतन / INSERT के साथ अपवाद

यह उदाहरण उपयुक्त के रूप में या तो अद्यतन करने के लिए अपवाद हैंडलिंग का उपयोग करता है:

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
        -- note that "a" must be unique
        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');

हैकर्स मेलिंग सूची में संभवतः 9.1 और इसके बाद के संस्करण में सीटीई का उपयोग करते हुए थोक में यह कैसे करना है, इसका एक उदाहरण है :

WITH foos AS (SELECT (UNNEST(%foo[])).*)
updated as (UPDATE foo SET foo.a = foos.a ... RETURNING foo.id)
INSERT INTO foo SELECT foos.* FROM foos LEFT JOIN updated USING(id)
WHERE updated.id IS NULL;

स्पष्ट उदाहरण के लिए a_horse_with_no_name का उत्तर देखें ।


7
केवल एक चीज जो मुझे पसंद नहीं है, वह यह है कि यह बहुत धीमी होगी, क्योंकि प्रत्येक अपटाउन डेटाबेस में अपना स्वयं का व्यक्तिगत कॉल होगा।
baash05

@ baash05 थोक में ऐसा करने का एक तरीका हो सकता है, मेरे अपडेट किए गए जवाब देखें।
स्टीफन डेने

2
केवल एक चीज जो मैं अलग तरीके से करूँगा, वह है कि केवल LOOP के बजाय 1..2 LOOP का उपयोग करें ताकि यदि कुछ अन्य अद्वितीय अवरोधों का उल्लंघन किया जाए तो यह अनिश्चित काल तक स्पिन नहीं होगा।
ओलामॉर्क

2
excludedयहाँ पहले समाधान में क्या संदर्भित है?
ichbinallen

2
डॉक्स में डायनबेलन सेट और जहां पर CONFLICT करते हैं, UPDATE को टेबल के नाम (या अन्य नाम) का उपयोग करके मौजूदा पंक्ति तक पहुंच है, और विशेष टेड टेबल का उपयोग करके सम्मिलन के लिए प्रस्तावित पंक्तियों के लिए है । इस मामले में, विशेष excludedतालिका आपको उन मूल्यों तक पहुंच प्रदान करती है जो आप पहली बार में INSERT में कोशिश कर रहे थे।
TMichel

429

चेतावनी: यह सुरक्षित नहीं है यदि एक ही समय में कई सत्रों से निष्पादित किया जाता है (नीचे दिए गए विवरण देखें)।


Postgresql में "UPSERT" करने का एक और चतुर तरीका दो क्रमिक UPDATE / INSERT कथन करना है जो प्रत्येक सफल होने के लिए तैयार किए गए हैं या जिनका कोई प्रभाव नहीं है।

UPDATE table SET field='C', field2='Z' WHERE id=3;
INSERT INTO table (id, field, field2)
       SELECT 3, 'C', 'Z'
       WHERE NOT EXISTS (SELECT 1 FROM table WHERE id=3);

UPDATE सफल होगा यदि "id = 3" वाली पंक्ति पहले से मौजूद है, अन्यथा इसका कोई प्रभाव नहीं है।

INSERT तभी सफल होगा जब "id = 3" वाली पंक्ति पहले से मौजूद न हो।

आप इन दोनों को एक स्ट्रिंग में जोड़ सकते हैं और अपने आवेदन से दोनों को एक ही एसक्यूएल स्टेटमेंट के साथ चला सकते हैं। उन्हें एक ही लेनदेन में एक साथ चलाने की अत्यधिक अनुशंसा की जाती है।

आइसोलेशन में या लॉक टेबल पर चलने पर यह बहुत अच्छी तरह से काम करता है, लेकिन दौड़ की स्थिति के अधीन है, जिसका अर्थ है कि यह अभी भी डुप्लिकेट कुंजी त्रुटि के साथ विफल हो सकता है अगर एक पंक्ति को समवर्ती रूप से डाला जाता है, या एक पंक्ति को समवर्ती रूप से हटाए जाने पर सम्मिलित पंक्ति के साथ समाप्त नहीं हो सकता है। । SERIALIZABLEPostgreSQL 9.1 या उच्चतर पर एक लेन-देन इसे बहुत उच्च क्रमिक विफलता दर की कीमत पर मज़बूती से संभाल लेगा, जिसका अर्थ है कि आपको बहुत अधिक प्रयास करना होगा। देखें कि उखाड़ना इतना जटिल क्यों है , जो इस मामले पर अधिक विस्तार से चर्चा करता है।

यह दृष्टिकोण भी है में खो अपडेट के अधीन read committedजब तक आवेदन चेकों प्रभावित पंक्ति में गिना जाता है और पुष्टि करता है कि या तो अलगाव insertया updateएक पंक्ति प्रभावित


6
संक्षिप्त उत्तर: यदि रिकॉर्ड मौजूद है तो INSERT कुछ नहीं करता है। लंबे उत्तर: INSERT में सेलेक्ट कई परिणाम के रूप में वापस आ जाएगा क्योंकि जहां क्लॉज के मैच हैं। यह सबसे अधिक है (यदि नंबर एक उप-चयन के परिणाम में नहीं है), अन्यथा शून्य। INSERT इस प्रकार एक या शून्य पंक्तियों को जोड़ देगा।
पीटर बेकर

3
'जहां' का उपयोग करके भाग को सरल बनाया जा सकता है:... where not exists (select 1 from table where id = 3);
एंडी तजहोनो

1
यह सही उत्तर होना चाहिए .. कुछ मामूली ट्वीक्स के साथ, यह एक बड़े पैमाने पर अपडेट करने के लिए इस्तेमाल किया जा सकता है .. हम्म .. मुझे आश्चर्य है कि अगर एक अस्थायी तालिका का उपयोग किया जा सकता है ..
baash05

1
@keaplogik, कि 9.1 सीमा लेखनीय CTE (सामान्य टेबल एक्सप्रेशन) के साथ है, जो अन्य उत्तरों में वर्णित है। इस उत्तर में प्रयुक्त वाक्यविन्यास बहुत बुनियादी है और लंबे समय से समर्थित है।
बोवाइन 19

8
चेतावनी, यह read committedअलगाव में खोए हुए अद्यतन के अधीन है जब तक कि आपका आवेदन यह सुनिश्चित करने के लिए जांच न कर ले कि insertया updateएक गैर-शून्य उपद्रवी है। देखें dba.stackexchange.com/q/78510/7788
क्रेग रिंगर

227

PostgreSQL 9.1 के साथ यह एक लेखन योग्य सीटीई ( सामान्य तालिका अभिव्यक्ति ) का उपयोग करके प्राप्त किया जा सकता है :

WITH new_values (id, field1, field2) as (
  values 
     (1, 'A', 'X'),
     (2, 'B', 'Y'),
     (3, 'C', 'Z')

),
upsert as
( 
    update mytable m 
        set field1 = nv.field1,
            field2 = nv.field2
    FROM new_values nv
    WHERE m.id = nv.id
    RETURNING m.*
)
INSERT INTO mytable (id, field1, field2)
SELECT id, field1, field2
FROM new_values
WHERE NOT EXISTS (SELECT 1 
                  FROM upsert up 
                  WHERE up.id = new_values.id)

इन ब्लॉग प्रविष्टियों को देखें:


ध्यान दें कि यह समाधान एक अद्वितीय कुंजी उल्लंघन को नहीं रोकता है लेकिन यह खोए हुए अपडेट के लिए असुरक्षित नहीं है। क्रेग रिंगर द्वारा dba.stackexchange.com पर अनुवर्ती
देखें


1
@ फ़्राँस्वाइसब्यूसोल: रेस की स्थिति की संभावना "कोशिश / संभाल अपवाद" दृष्टिकोण से बहुत छोटी है
a_horse_with_no_name

2
@a_horse_with_no_name आप बिल्कुल कैसे मतलब है कि दौड़ की स्थिति पर मौका बहुत छोटा है? जब मैं इस क्वेरी को समान रिकॉर्ड के साथ समवर्ती रूप से निष्पादित करता हूं, तो मुझे त्रुटि मिल रही है "डुप्लिकेट कुंजी मान अद्वितीय बाधा का उल्लंघन करता है" जब तक कि रिकॉर्ड का पता नहीं चलता है कि रिकॉर्ड सम्मिलित किया गया है। क्या यह एक पूर्ण उदाहरण है?
जीरो वैन डीजक

4
@a_horse_with_no_name आपका समाधान समवर्ती स्थितियों में काम करने के लिए लगता है जब आप निम्न लॉक के साथ मुखर बयान को लपेटते हैं: BEGIN WORK; शेयर की तुलना में मेरी पसंद को कम करने के लिए अतिरिक्त मोड; <UPSERT HERE>; काम करना;
जीरो वैन डिजक

2
@JeroenvanDijk: धन्यवाद। "बहुत छोटे" से मेरा तात्पर्य यह है कि यदि इस से कई लेन-देन (और परिवर्तन करने के लिए!) अद्यतन और सम्मिलित करने के बीच का समय छोटा है क्योंकि सब कुछ सिर्फ एक बयान है। आप हमेशा दो स्वतंत्र INSERT बयानों द्वारा एक pk उल्लंघन उत्पन्न कर सकते हैं। यदि आप पूरी तालिका को लॉक करते हैं, तो आप इसे प्रभावी रूप से सभी एक्सेस एक्सेस करते हैं (ऐसा कुछ जो आप क्रमिक अलगाव स्तर के साथ भी प्राप्त कर सकते हैं)।
a_horse_with_no_name

12
यह समाधान खोए हुए अपडेट के अधीन है यदि डालने वाला लेन-देन वापस आ जाता है; लागू करने के लिए कोई जाँच नहीं है कि UPDATEप्रभावित कोई पंक्तियाँ।
क्रेग रिंगर

132

PostgreSQL 9.5 और नए में आप उपयोग कर सकते हैं INSERT ... ON CONFLICT UPDATE

दस्तावेज देखें ।

एक MySQL INSERT ... ON DUPLICATE KEY UPDATEसीधे एक को rephrased किया जा सकता है ON CONFLICT UPDATE। न ही SQL- मानक वाक्यविन्यास है, वे दोनों डेटाबेस-विशिष्ट एक्सटेंशन हैं। इसके लिए अच्छे कारणों MERGEका उपयोग नहीं किया गया था , एक नया वाक्यविन्यास सिर्फ मनोरंजन के लिए नहीं बनाया गया था। (MySQL के सिंटैक्स में ऐसे मुद्दे भी हैं जिनका अर्थ है कि इसे सीधे नहीं अपनाया गया था)।

उदाहरण के लिए दिया गया सेटअप:

CREATE TABLE tablename (a integer primary key, b integer, c integer);
INSERT INTO tablename (a, b, c) values (1, 2, 3);

MySQL क्वेरी:

INSERT INTO tablename (a,b,c) VALUES (1,2,3)
  ON DUPLICATE KEY UPDATE c=c+1;

हो जाता है:

INSERT INTO tablename (a, b, c) values (1, 2, 10)
ON CONFLICT (a) DO UPDATE SET c = tablename.c + 1;

अंतर:

  • आप चाहिए स्तंभ नाम (या अद्वितीय बाधा नाम) विशिष्टता जांच करने के लिए उपयोग करने के लिए निर्दिष्ट करें। वह हैON CONFLICT (columnname) DO

  • कीवर्ड SETका उपयोग किया जाना चाहिए, जैसे कि यह एक सामान्य UPDATEकथन था

इसकी कुछ अच्छी विशेषताएं भी हैं:

  • तुम एक हो सकता है WHEREअपने पर खंड UPDATE(सूचना देने के लिए प्रभावी रूप से बारी ON CONFLICT UPDATEमें ON CONFLICT IGNOREनिश्चित मूल्यों के लिए)

  • प्रस्तावित-इन-सम्मिलन मान पंक्ति-चर के रूप में उपलब्ध हैं EXCLUDED, जिसमें लक्ष्य तालिका के समान संरचना है। आप तालिका नाम का उपयोग करके तालिका में मूल मान प्राप्त कर सकते हैं। तो इस मामले में EXCLUDED.cहोगा 10(क्योंकि यही हमने सम्मिलित करने का प्रयास किया है) और "table".cहोगा 3क्योंकि तालिका में वर्तमान मूल्य है। आप SETएक्सप्रेशन और WHEREक्लॉज़ दोनों में से किसी एक का उपयोग कर सकते हैं ।

पोस्टग्रेएसक्यूएल में अपग्रेड पर पृष्ठभूमि के लिए देखें कि कैसे UPSERT (MERGE, INSERT ... ON DUPLICATE UPDATE)?


मैंने PostgreSQL के 9.5 समाधान में देखा है जैसा कि आपने ऊपर वर्णित किया है क्योंकि मैं एसक्यूएल के तहत ऑटो वृद्धि क्षेत्र में अंतराल का अनुभव कर रहा था ON DUPLICATE KEY UPDATE। मैंने Postgres 9.5 डाउनलोड किया है और आपके कोड को लागू किया है, लेकिन विचित्र रूप से Postgres के तहत एक ही समस्या होती है: प्राथमिक कुंजी का सीरियल फ़ील्ड लगातार नहीं है (आवेषण और अपडेट के बीच अंतराल हैं।)। किसी भी विचार यहाँ क्या हो रहा है? क्या यह सामान्य है? किसी भी विचार कैसे इस व्यवहार से बचने के लिए? धन्यवाद।
WM

@M कि एक मुखर ऑपरेशन के लिए बहुत अधिक अंतर्निहित है। आपको उस फ़ंक्शन का मूल्यांकन करना होगा जो डालने का प्रयास करने से पहले अनुक्रम उत्पन्न करता है। चूंकि इस तरह के अनुक्रमों को समवर्ती रूप से संचालित करने के लिए डिज़ाइन किया गया है, इसलिए वे सामान्य लेनदेन शब्दार्थ से छूटते हैं, लेकिन भले ही वे पीढ़ी नहीं थे एक सबट्रैक्शन में नहीं कहा जाता है और वापस रोल किया जाता है, यह सामान्य रूप से पूरा होता है और बाकी ऑपरेशन के साथ होता है। तो यह "अंतरहीन" अनुक्रम कार्यान्वयन के साथ भी होगा। एकमात्र तरीका डीबी इससे बच सकता है जब तक कि प्रमुख जांच के बाद अनुक्रम पीढ़ी के मूल्यांकन में देरी न हो।
क्रेग रिंगर

1
@ अपनी समस्याएँ पैदा करेगा। मूल रूप से, आप फंस गए हैं। लेकिन अगर आप सीरियल / ऑटो_इन्क्रिमेंट पर निर्भर हो रहे हैं तो आप पहले से ही बग्स से दूर हो चुके हैं। आप क्षणिक त्रुटियों सहित पुनरावर्तन की वजह से अनुक्रम अंतराल हो सकता है - लोड के अंतर्गत रिबूट, ग्राहक त्रुटियों मध्य लेन-देन, दुर्घटनाओं, आदि आप कभी भी, किसी पर भरोसा कभी SERIAL/ SEQUENCEया AUTO_INCREMENTहोने अंतराल नहीं। यदि आपको गैपलेस सीक्वेंस चाहिए तो वे अधिक जटिल हैं; आपको आमतौर पर एक काउंटर टेबल का उपयोग करने की आवश्यकता होती है। Google आपको और बताएगा। लेकिन जागरूक अंतरविहीन दृश्यों को सभी सम्मिलित संगति को रोकें।
क्रेग रिंगर

@ यदि आपको बिल्कुल गैपलेस सीक्वेंस और अपग्रेड की आवश्यकता है, तो आप मैन्युअल में चर्चा किए गए फंक्शन-बेस्ड अप्रोच अप्रोच का उपयोग कर सकते हैं, जिसमें एक गैपलेस सीक्वेंस इम्प्लीमेंटेशन है जो काउंटर टेबल का उपयोग करता है। क्योंकि BEGIN ... EXCEPTION ...एक सबट्रांसक्शन में रन जो गलती से वापस लुढ़क जाता है, अगर INSERTअसफल हुआ तो आपका अनुक्रम वृद्धि वापस लुढ़क जाएगी ।
क्रेग रिंगर

बहुत बहुत धन्यवाद @ क्रेग रिंगर, यह बहुत जानकारीपूर्ण था। मुझे एहसास हुआ कि मैं बस उस ऑटो वेतन वृद्धि प्राथमिक कुंजी होने पर छोड़ सकता हूँ। मैंने 3 क्षेत्रों का एक समग्र प्राथमिक बनाया और अपनी विशेष वर्तमान आवश्यकता के लिए, एक अंतररहित ऑटो वेतन वृद्धि क्षेत्र की वास्तव में कोई आवश्यकता नहीं है। फिर से धन्यवाद, आपके द्वारा प्रदान की गई जानकारी मुझे भविष्य में एक प्राकृतिक और स्वस्थ DB व्यवहार को रोकने की कोशिश कर रही समय की बचत करेगी। मैं अब इसे बेहतर समझ रहा हूं।
WM

17

जब मैं यहां आया था तो मैं उसी चीज की तलाश कर रहा था, लेकिन एक सामान्य "अपटाउन" फंक्शन की कमी ने मुझे थोड़ा परेशान कर दिया था, इसलिए मुझे लगा कि आप सिर्फ अपडेट पास कर सकते हैं और उस फंक्शन पर तर्क के रूप में एसक्यूएल डाल सकते हैं, जो मैनुअल है।

यह इस तरह दिखेगा:

CREATE FUNCTION upsert (sql_update TEXT, sql_insert TEXT)
    RETURNS VOID
    LANGUAGE plpgsql
AS $$
BEGIN
    LOOP
        -- first try to update
        EXECUTE sql_update;
        -- check if the row is found
        IF FOUND THEN
            RETURN;
        END IF;
        -- not found so insert the row
        BEGIN
            EXECUTE sql_insert;
            RETURN;
            EXCEPTION WHEN unique_violation THEN
                -- do nothing and loop
        END;
    END LOOP;
END;
$$;

और शायद वह करने के लिए जो आप शुरू में करना चाहते थे, बैच "upsert", आप Tcl का उपयोग sql_update को विभाजित करने और व्यक्तिगत अपडेट को लूप करने के लिए कर सकते हैं, प्रीफॉर्मेंस हिट बहुत छोटा होगा http://archives.postgresqb.org/pgsql- प्रदर्शन / 2006-04 / msg00557.php

उच्चतम लागत आपके कोड से क्वेरी निष्पादित कर रही है, डेटाबेस पक्ष पर निष्पादन लागत बहुत छोटी है


3
आपको अभी भी इसे एक रिट्री लूप में चलाना है और DELETEजब तक आप टेबल को लॉक नहीं करते या SERIALIZABLEPostgreSQL 9.1 या उससे अधिक पर लेन-देन अलगाव में नहीं होते, तब तक समवर्ती के साथ दौड़ने का खतरा होता है।
क्रेग रिंगर

13

इसे करने के लिए कोई सरल कमांड नहीं है।

डॉक्स से एक की तरह, फ़ंक्शन का उपयोग करने के लिए सबसे सही दृष्टिकोण है ।

एक अन्य समाधान (हालांकि यह सुरक्षित नहीं है) रिटर्निंग के साथ अपडेट करना है, यह जांचें कि कौन सी पंक्तियों को अपडेट किया गया था, और उनमें से बाकी सम्मिलित करें

की तर्ज पर कुछ:

update table
set column = x.column
from (values (1,'aa'),(2,'bb'),(3,'cc')) as x (id, column)
where table.id = x.id
returning id;

आईडी मानकर: 2 लौटाया गया:

insert into table (id, column) values (1, 'aa'), (3, 'cc');

बेशक यह जल्दी या बाद में (समवर्ती वातावरण में) बाहर निकल जाएगा, क्योंकि यहां स्पष्ट दौड़ की स्थिति है, लेकिन आमतौर पर यह काम करेगा।

यहाँ विषय पर एक लंबा और अधिक व्यापक लेख है


1
यदि इस विकल्प का उपयोग कर रहे हैं, तो सुनिश्चित करें कि अपडेट वापस कर दिया गया है भले ही आईडी कुछ भी न हो। मैंने डेटाबेस को "अपडेट टेबल फू सेट बार = 4 जहां बार = 4" जैसे प्रश्नों को अनुकूलित किया है।
14

10

व्यक्तिगत रूप से, मैंने एक "नियम" डाला है जो सम्मिलित विवरण से जुड़ा हुआ है। कहते हैं कि आपके पास एक "डीएनएस" तालिका थी जो प्रति ग्राहक के आधार पर प्रति समय हिट हिट दर्ज की गई थी:

CREATE TABLE dns (
    "time" timestamp without time zone NOT NULL,
    customer_id integer NOT NULL,
    hits integer
);

आप अद्यतन मानों के साथ पंक्तियों को फिर से सम्मिलित करने में सक्षम होना चाहते थे, या यदि वे पहले से मौजूद नहीं थे, तो उन्हें बनाएं। Customer_id और समय पर बंद किया गया। कुछ इस तरह:

CREATE RULE replace_dns AS 
    ON INSERT TO dns 
    WHERE (EXISTS (SELECT 1 FROM dns WHERE ((dns."time" = new."time") 
            AND (dns.customer_id = new.customer_id)))) 
    DO INSTEAD UPDATE dns 
        SET hits = new.hits 
        WHERE ((dns."time" = new."time") AND (dns.customer_id = new.customer_id));

अद्यतन: यह विफल होने की क्षमता है अगर एक साथ आवेषण हो रहे हैं, क्योंकि यह अनूठे_विभाजन के अपवाद उत्पन्न करेगा। हालांकि, गैर-समाप्त लेन-देन जारी रहेगा और सफल होगा, और आपको केवल समाप्त लेनदेन को दोहराने की आवश्यकता है।

हालाँकि, यदि हर समय कई आवेषण हो रहे हैं, तो आप आवेषण कथनों के चारों ओर एक टेबल लॉक लगाना चाहेंगे: SHAR ROW EXCLUSIVE लॉकिंग आपके लक्ष्य तालिका में पंक्तियों को डालने, हटाने या अपडेट करने वाले किसी भी संचालन को रोक देगी। हालांकि, अद्वितीय कुंजी को अपडेट नहीं करने वाले अपडेट सुरक्षित हैं, इसलिए यदि आप कोई ऑपरेशन नहीं करेंगे, तो इसके बजाय सलाहकार ताले का उपयोग करें।

इसके अलावा, COPY कमांड RULES का उपयोग नहीं करता है, इसलिए यदि आप COPY के साथ सम्मिलित कर रहे हैं, तो आपको इसके बजाय ट्रिगर्स का उपयोग करना होगा।


9

मैं इस फ़ंक्शन मर्ज का उपयोग करता हूं

CREATE OR REPLACE FUNCTION merge_tabla(key INT, data TEXT)
  RETURNS void AS
$BODY$
BEGIN
    IF EXISTS(SELECT a FROM tabla WHERE a = key)
        THEN
            UPDATE tabla SET b = data WHERE a = key;
        RETURN;
    ELSE
        INSERT INTO tabla(a,b) VALUES (key, data);
        RETURN;
    END IF;
END;
$BODY$
LANGUAGE plpgsql

1
यह केवल updateपहले करने के लिए अधिक कुशल है और फिर अद्यतन पंक्तियों की संख्या की जांच करें। (अहमद का जवाब देखें)
a_horse_with_no_name

8

यदि आप INSERT और REPLACE करना चाहते हैं तो मैं ऊपर "फ़ंक्शन" को कस्टम करता हूं:

`

 CREATE OR REPLACE FUNCTION upsert(sql_insert text, sql_update text)

 RETURNS void AS
 $BODY$
 BEGIN
    -- first try to insert and after to update. Note : insert has pk and update not...

    EXECUTE sql_insert;
    RETURN;
    EXCEPTION WHEN unique_violation THEN
    EXECUTE sql_update; 
    IF FOUND THEN 
        RETURN; 
    END IF;
 END;
 $BODY$
 LANGUAGE plpgsql VOLATILE
 COST 100;
 ALTER FUNCTION upsert(text, text)
 OWNER TO postgres;`

और निष्पादित करने के बाद, कुछ इस तरह से करें:

SELECT upsert($$INSERT INTO ...$$,$$UPDATE... $$)

संकलक त्रुटियों से बचने के लिए डबल डॉलर-कॉमा डालना महत्वपूर्ण है

  • गति की जाँच करें ...

7

सबसे ज्यादा पसंद किए जाने वाले उत्तर के समान, लेकिन थोड़ा तेज काम करता है:

WITH upsert AS (UPDATE spider_count SET tally=1 WHERE date='today' RETURNING *)
INSERT INTO spider_count (spider, tally) SELECT 'Googlebot', 1 WHERE NOT EXISTS (SELECT * FROM upsert)

(स्रोत: http://www.the-art-of-web.com/sql/upsert/ )


3
दो सत्रों में समवर्ती चलाने पर यह विफल हो जाएगा, क्योंकि न तो अपडेट में मौजूदा पंक्ति दिखाई देगी, इसलिए दोनों अपडेट शून्य पंक्तियों को टकराएंगे, इसलिए दोनों प्रश्न एक प्रविष्टि जारी करेंगे।
क्रेग रिंगर

6

नाम मान जोड़े के रूप में खाता सेटिंग प्रबंधित करने के लिए मेरे पास एक ही समस्या है। डिजाइन मानदंड यह है कि अलग-अलग ग्राहकों के पास अलग-अलग सेटिंग्स सेट हो सकते हैं।

JWP के समान मेरा समाधान, आपके एप्लिकेशन के भीतर मर्ज रिकॉर्ड बनाते हुए, उसे मिटा और बदल देना है।

यह सुंदर बुलेटप्रूफ है, प्लेटफार्म स्वतंत्र है और चूंकि प्रति ग्राहक लगभग 20 से अधिक सेटिंग्स कभी नहीं होती हैं, यह केवल 3 काफी कम लोड डीबी कॉल है - शायद सबसे तेज़ विधि।

व्यक्तिगत पंक्तियों को अपडेट करने का विकल्प - अपवादों के लिए जाँच करना - फिर सम्मिलित करना - या कुछ संयोजन छिपा हुआ कोड है, धीमा और अक्सर टूट जाता है क्योंकि (जैसा कि ऊपर उल्लेख किया गया है) गैर मानक SQL अपवाद db से db में बदल रहा है - या यहां तक ​​कि रिलीज करने के लिए भी जारी है।

 #This is pseudo-code - within the application:
 BEGIN TRANSACTION - get transaction lock
 SELECT all current name value pairs where id = $id into a hash record
 create a merge record from the current and update record
  (set intersection where shared keys in new win, and empty values in new are deleted).
 DELETE all name value pairs where id = $id
 COPY/INSERT merged records 
 END TRANSACTION

एसओ में आपका स्वागत है। अच्छा परिचय! :-)
डॉन प्रश्न

1
यह इस तरह REPLACE INTOसे अधिक है INSERT INTO ... ON DUPLICATE KEY UPDATE, जो ट्रिगर का उपयोग करने पर समस्या पैदा कर सकता है। आप अपडेट हटाने की बजाय, डिलीट रनिंग को समाप्त करेंगे और ट्रिगर / नियम सम्मिलित करेंगे।
cHao

5

बयान के PostgreSQL प्रलेखन केINSERT अनुसार , ON DUPLICATE KEYमामले को संभालने का समर्थन नहीं किया जाता है। सिंटैक्स का वह भाग एक मालिकाना MySQL एक्सटेंशन है।


@ ल्यूकियन MERGEभी वास्तव में एक ओएलएपी ऑपरेशन के अधिक है; स्पष्टीकरण के लिए stackoverflow.com/q/17267417/398670 देखें । यह संगामिति शब्दार्थ को परिभाषित नहीं करता है और ज्यादातर लोग जो इसे मुखर के लिए उपयोग करते हैं, वे केवल बग बना रहे हैं।
क्रेग रिंगर

5
CREATE OR REPLACE FUNCTION save_user(_id integer, _name character varying)
  RETURNS boolean AS
$BODY$
BEGIN
    UPDATE users SET name = _name WHERE id = _id;
    IF FOUND THEN
        RETURN true;
    END IF;
    BEGIN
        INSERT INTO users (id, name) VALUES (_id, _name);
    EXCEPTION WHEN OTHERS THEN
            UPDATE users SET name = _name WHERE id = _id;
        END;
    RETURN TRUE;
END;

$BODY$
  LANGUAGE plpgsql VOLATILE STRICT

5

छोटे सेट को मर्ज करने के लिए, उपरोक्त फ़ंक्शन का उपयोग करना ठीक है। हालांकि, यदि आप बड़ी मात्रा में डेटा मर्ज कर रहे हैं, तो मैं http://mbk.projects.postgresql.org पर देखने का सुझाव दूंगा

वर्तमान सर्वोत्तम प्रथा जो मुझे ज्ञात है:

  1. अस्थायी तालिका में नए / अपडेट किए गए डेटा की प्रतिलिपि बनाएं (यदि लागत ठीक है तो आप INSERT कर सकते हैं)
  2. अधिग्रहण ताला [वैकल्पिक] (सलाहकार तालिका ताले, IMO के लिए बेहतर है)
  3. मर्ज। (मज़ेदार हिस्सा)

5

UPDATE संशोधित पंक्तियों की संख्या लौटाएगा। यदि आप JDBC (जावा) का उपयोग करते हैं, तो आप इस मान को 0 के विरुद्ध जाँच सकते हैं और यदि कोई पंक्तियाँ प्रभावित नहीं हुई हैं, तो इसके बजाय INSERT को फायर करें। यदि आप कुछ अन्य प्रोग्रामिंग भाषा का उपयोग करते हैं, तो शायद संशोधित पंक्तियों की संख्या अभी भी प्राप्त की जा सकती है, प्रलेखन की जांच करें।

यह उतना सुरुचिपूर्ण नहीं हो सकता है लेकिन आपके पास बहुत सरल SQL है जो कॉलिंग कोड से उपयोग करने के लिए अधिक तुच्छ है। अलग-अलग, यदि आप पीएल / पीएसक्यूएल में दस लाइन की पटकथा लिखते हैं, तो आपको संभवतः केवल इसके लिए एक या किसी अन्य प्रकार की एक इकाई परीक्षण करना चाहिए।


4

संपादित करें: यह अपेक्षा के अनुरूप काम नहीं करता है। स्वीकृत उत्तर के विपरीत, यह अद्वितीय कुंजी उल्लंघन पैदा करता है जब दो प्रक्रियाएं बार-बार कॉल करती हैंupsert_foo समवर्ती रूप से ।

यूरेका! मैंने एक तरीके से इसे एक क्वेरी में किया: UPDATE ... RETURNINGयदि कोई पंक्तियाँ प्रभावित हुईं तो परीक्षण करने के लिए उपयोग करें:

CREATE TABLE foo (k INT PRIMARY KEY, v TEXT);

CREATE FUNCTION update_foo(k INT, v TEXT)
RETURNS SETOF INT AS $$
    UPDATE foo SET v = $2 WHERE k = $1 RETURNING $1
$$ LANGUAGE sql;

CREATE FUNCTION upsert_foo(k INT, v TEXT)
RETURNS VOID AS $$
    INSERT INTO foo
        SELECT $1, $2
        WHERE NOT EXISTS (SELECT update_foo($1, $2))
$$ LANGUAGE sql;

UPDATEएक अलग प्रक्रिया में किया जा सकता है क्योंकि, दुर्भाग्य से, यह एक सिंटैक्स त्रुटि है:

... WHERE NOT EXISTS (UPDATE ...)

अब यह वांछित के रूप में काम करता है:

SELECT upsert_foo(1, 'hi');
SELECT upsert_foo(1, 'bye');
SELECT upsert_foo(3, 'hi');
SELECT upsert_foo(3, 'bye');

1
यदि आप एक लेखन योग्य सीटीई का उपयोग करते हैं, तो आप उन्हें एक बयान में जोड़ सकते हैं। लेकिन यहां पोस्ट किए गए अधिकांश समाधानों की तरह, यह एक गलत है और समवर्ती अपडेट की उपस्थिति में विफल हो जाएगा।
क्रेग रिंगर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.