SQLite - UPSERT * नहीं * INSERT या REPLACE


535

http://en.wikipedia.org/wiki/Upsert

SQL सर्वर पर अद्यतन संग्रहीत संग्रह सम्मिलित करें

क्या SQLite में ऐसा करने का कोई चतुर तरीका है जिसके बारे में मैंने नहीं सोचा है?

मूल रूप से मैं चार कॉलम में से तीन को अपडेट करना चाहता हूं यदि रिकॉर्ड मौजूद है, अगर यह मौजूद नहीं है तो मैं चौथे कॉलम के लिए डिफ़ॉल्ट (एनयूएल) मान के साथ रिकॉर्ड को सम्मिलित करना चाहता हूं।

आईडी एक प्राथमिक कुंजी है, इसलिए यूपीएसईआरटी का केवल एक ही रिकॉर्ड होगा।

(यदि मुझे स्पष्ट रूप से अद्यतन करने की आवश्यकता है या नहीं, यह निर्धारित करने के लिए मैं चयन के ओवरहेड से बचने की कोशिश कर रहा हूं)

सुझाव?


मैं पुष्टि नहीं कर सकता कि टेबल साइट के लिए SQLite साइट पर सिंटैक्स। मैंने इसका परीक्षण करने के लिए डेमो नहीं बनाया है, लेकिन यह समर्थित नहीं लगता है।

यदि यह था, तो मेरे पास तीन कॉलम हैं, तो यह वास्तव में ऐसा दिखेगा:

CREATE TABLE table1( 
    id INTEGER PRIMARY KEY ON CONFLICT REPLACE, 
    Blob1 BLOB ON CONFLICT REPLACE, 
    Blob2 BLOB ON CONFLICT REPLACE, 
    Blob3 BLOB 
);

लेकिन पहली दो बूँदें संघर्ष का कारण नहीं बनेंगी, केवल ID ही होगी इसलिए मैं Blob1 और Blob2 को प्रतिस्थापित नहीं करूँगा (वांछित के रूप में)


बाइंडिंग डेटा में UPDATEs एक पूर्ण लेनदेन है, जिसका अर्थ है कि अद्यतन की जाने वाली प्रत्येक प्रेषित पंक्ति की आवश्यकता है: INSERT के विपरीत स्टेटमेंट तैयार / बिंद / चरण / अंतिम रूप दें जो रीसेट फ़ंक्शन के उपयोग की अनुमति देता है

स्टेटमेंट ऑब्जेक्ट का जीवन कुछ इस तरह से होता है:

  1. Sqlite3_prepare_v2 () का उपयोग करके ऑब्जेक्ट बनाएं
  2. Sqlite3_bind_ इंटरफेस का उपयोग कर मापदंडों को होस्ट करने के लिए मानों को बांधें।
  3. Sqlite3_step () कॉल करके SQL चलाएँ
  4. Sqlite3_reset () का उपयोग करके कथन को रीसेट करें और फिर चरण 2 पर जाएं और दोहराएं।
  5. Sqlite3_finalize () का उपयोग करते हुए स्टेटमेंट ऑब्जेक्ट को नष्ट करें।

अद्यतन मैं अनुमान लगा रहा हूं कि INSERT की तुलना में धीमा है, लेकिन प्राथमिक कुंजी का उपयोग करके SELECT की तुलना कैसे करता है?

शायद मुझे 4 वें कॉलम (Blob3) को पढ़ने के लिए चयन का उपयोग करना चाहिए और फिर पहले 3 कॉलम के लिए नए डेटा के साथ मूल 4 वें कॉलम को सम्मिश्रित करते हुए एक नया रिकॉर्ड लिखने के लिए REPLACE का उपयोग करना चाहिए?


5
SQLite - UPSERT पूर्व-रिलीज़ संदर्भ में उपलब्ध है: sqlite.1065341.n5.nabble.com/…
गौरव

3
UPSERT SQLite के संस्करण 3.24.0 में उपलब्ध
pablo_worker

जवाबों:


854

तालिका में तीन कॉलम मानकर: ID, NAME, ROLE


BAD: यह ID = 1 के नए मानों के साथ सभी कॉलमों को सम्मिलित या प्रतिस्थापित करेगा:

INSERT OR REPLACE INTO Employee (id, name, role) 
  VALUES (1, 'John Foo', 'CEO');

BAD: यह कॉलम में से 2 को सम्मिलित या प्रतिस्थापित करेगा ... NAME कॉलम NULL या डिफ़ॉल्ट मान पर सेट किया जाएगा:

INSERT OR REPLACE INTO Employee (id, role) 
  VALUES (1, 'code monkey');

GOOD : SQLite में संघर्ष खंड UPSERT समर्थन पर SQLite का उपयोग करें ! UPSERT सिंटैक्स को 3.24.0 संस्करण के साथ SQLite में जोड़ा गया था!

UPSERT INSERT के लिए एक विशेष वाक्यविन्यास जोड़ है जो INSERT को UPDATE या नो-ऑप के रूप में व्यवहार करने का कारण बनता है यदि INSERT एक अद्वितीय बाधा का उल्लंघन करता है। UPSERT मानक SQL नहीं है। SQLite में UPSERT, PostgreSQL द्वारा स्थापित सिंटैक्स का अनुसरण करता है।

यहाँ छवि विवरण दर्ज करें

अच्छा लेकिन कोमल: यह कॉलम के 2 अपडेट करेगा। जब ID = 1 मौजूद होगा, NAME अप्रभावित रहेगा। जब ID = 1 मौजूद नहीं होगा, तो नाम डिफ़ॉल्ट (NULL) होगा।

INSERT OR REPLACE INTO Employee (id, role, name) 
  VALUES (  1, 
            'code monkey',
            (SELECT name FROM Employee WHERE id = 1)
          );

यह कॉलम के 2 को अपडेट करेगा। जब ID = 1 मौजूद होता है, तो ROLE अप्रभावित रहेगा। जब ID = 1 मौजूद नहीं होता है, तो भूमिका डिफ़ॉल्ट मान के बजाय 'Benchwarmer' पर सेट की जाएगी।

INSERT OR REPLACE INTO Employee (id, name, role) 
  VALUES (  1, 
            'Susan Bar',
            COALESCE((SELECT role FROM Employee WHERE id = 1), 'Benchwarmer')
          );

33
+1 शानदार! यदि आप किसी भी क्षेत्र के लिए पुराने मूल्य और नए मूल्य को संयोजित करने / तुलना करने की आवश्यकता है, तो एम्बेडेड चयन क्लॉज आपको CONFLICT REPLACE कार्यक्षमता पर डिफ़ॉल्ट को ओवरराइड करने की सुविधा देता है।
16

24
यदि कर्मचारी को अन्य पंक्तियों द्वारा कैस्केडिंग विलोपन के साथ संदर्भित किया जाता है, तो अन्य पंक्तियों को अभी भी प्रतिस्थापन द्वारा हटा दिया जाएगा।
डॉन रेबा

246
यह तकलीफ देता है। SQlite को UPSERT की जरूरत है।
औरिग जूल 10'12

21
क्या आप बता सकते हैं कि यह ID = 1 के लिए सभी कॉलमों को नए मानों के साथ सम्मिलित या प्रतिस्थापित क्यों करेगा: आपके पहले उदाहरण में BAD माना जाता है ? आपके द्वारा वहां उपस्थित कमांड आईडी 1 के साथ एक नया रिकॉर्ड बनाने के लिए है, जॉन फू और रोल सीईओ का नाम , या उस रिकॉर्ड को अधिलेखित करें, जिसकी आईडी 1 है, अगर यह पहले से ही है, तो उस डेटा के साथ ( आईडी मानकर प्राथमिक कुंजी है) । तो, यदि ऐसा होता है तो यह बुरा क्यों है?
या मैपर

10
@ कॉर्नेलियस: यह स्पष्ट है, लेकिन ऐसा नहीं है कि पहले उदाहरण में क्या होता है। पहला उदाहरण सभी स्तंभों को जबरन सेट करने के लिए है, जो कि वास्तव में होता है, चाहे कोई भी रिकॉर्ड डाला गया हो या प्रतिस्थापित किया गया हो। तो, क्यों बुरा माना जाता है? लिंक किए गए उत्तर केवल यह भी बताते हैं कि कॉलम के सबसेट को निर्दिष्ट करते समय कुछ बुरा क्यों हो सकता है, जैसे आपके दूसरे उदाहरण में; सभी स्तंभों के लिए मान निर्दिष्ट करते हुए आपके पहले उदाहरण में क्या होता है, इसके किसी भी बुरे प्रभाव के बारे में विस्तार से नहीं लगता है । INSERT OR REPLACE
या मैपर

134

INSERT या REPLACE "UPSERT" के बराबर नहीं है

कहो कि मेरे पास आईडी, नाम और भूमिका के साथ कर्मचारी तालिका है:

INSERT OR REPLACE INTO Employee ("id", "name", "role") VALUES (1, "John Foo", "CEO")
INSERT OR REPLACE INTO Employee ("id", "role") VALUES (1, "code monkey")

बूम, आपने कर्मचारी नंबर 1 का नाम खो दिया है। SQLite ने इसे डिफ़ॉल्ट मान से बदल दिया है।

एक यूपीएसईआरटी का अपेक्षित उत्पादन भूमिका को बदलने और नाम रखने के लिए होगा।


21
-1 से मैं डरता हूं। हाँ स्वीकृत उत्तर गलत है, लेकिन जब तक आपका उत्तर समस्या को इंगित करता है तब तक इसका उत्तर नहीं है। एक वास्तविक उत्तर के लिए, एरिक बी के एम्बेडेड coalesce((select ..), 'new value')क्लॉज का उपयोग करते हुए चतुर समाधान देखें । मुझे लगता है कि एरिक के जवाब को अधिक वोटों की आवश्यकता है
दिन

22
वास्तव में। एरिक का निश्चित रूप से सबसे अच्छा जवाब और अधिक वोटों का हकदार है। यह कहा जा रहा है, मुझे लगता है कि समस्या को इंगित करके, मैंने अच्छा जवाब खोजने में थोड़ा योगदान दिया है (एरिक का जवाब बाद में आया, और मेरे जवाब में नमूना sql तालिकाओं पर बना है)। तो यकीन नहीं है कि अगर मैं एक -1 लायक हूं, लेकिन कोई बात नहीं :)
gregschlom

6
+1 से ऊपर की ओर ऑफसेट करने के लिए। जबरदस्त हंसी। हालांकि इस धागे पर दिलचस्प समयरेखा। स्पष्ट रूप से आपका जवाब एरिक के एक सप्ताह पहले खत्म हो गया था, लेकिन आप दोनों ने सवाल के लगभग दो साल बाद इसका जवाब दिया था। एरिक के लिए भी हालांकि विस्तृत के लिए +1।
अमीर


2
@ क्यू नहीं, क्योंकि डिलीट + इंसर्ट (जो कि एक रिप्लेस है) 2 डीएमएल स्टेटमेंट हैं, उदाहरण के लिए उनके अपने ट्रिगर्स। यह केवल 1 अपडेट स्टेटमेंट के समान नहीं है।
सेबस

109

यदि आप मौजूदा पंक्ति से सिर्फ एक या शायद दो कॉलम संरक्षित करना चाहते हैं तो एरिक बी का जवाब ठीक है। यदि आप बहुत सारे स्तंभों को संरक्षित करना चाहते हैं, तो यह बहुत तेज़ हो जाता है।

यहाँ एक दृष्टिकोण है जो किसी भी तरफ कॉलम की किसी भी राशि के लिए अच्छा होगा। इसका वर्णन करने के लिए मैं निम्नलिखित स्कीमा मानूंगा:

 CREATE TABLE page (
     id      INTEGER PRIMARY KEY,
     name    TEXT UNIQUE,
     title   TEXT,
     content TEXT,
     author  INTEGER NOT NULL REFERENCES user (id),
     ts      TIMESTAMP DEFAULT CURRENT_TIMESTAMP
 );

विशेष रूप से ध्यान दें कि nameपंक्ति की प्राकृतिक कुंजी है - idका उपयोग केवल विदेशी कुंजी के लिए किया जाता है, इसलिए यह बिंदु SQLite के लिए एक नई पंक्ति सम्मिलित करते समय आईडी मूल्य को स्वयं लेने के लिए है। लेकिन जब इसके आधार पर मौजूदा पंक्ति को अपडेट किया जाता है name, तो मैं चाहता हूं कि यह पुराने आईडी मान (स्पष्ट रूप से!) को जारी रखे।

मैं UPSERTनिम्नलिखित निर्माण के साथ एक सच्चा हासिल करता हूं :

 WITH new (name, title, author) AS ( VALUES('about', 'About this site', 42) )
 INSERT OR REPLACE INTO page (id, name, title, content, author)
 SELECT old.id, new.name, new.title, old.content, new.author
 FROM new LEFT JOIN page AS old ON new.name = old.name;

इस क्वेरी का सटीक रूप थोड़ा भिन्न हो सकता है। कुंजी INSERT SELECTमौजूदा मूल्यों को नए मूल्यों में शामिल करने के लिए, बाईं बाहरी जुड़ाव के साथ उपयोग होती है ।

यहां, यदि एक पंक्ति पहले मौजूद नहीं थी, तो old.idहोगी NULLऔर SQLite तब स्वचालित रूप से एक आईडी असाइन करेगा, लेकिन अगर पहले से ही ऐसी कोई पंक्ति थी, old.idतो इसका वास्तविक मूल्य होगा और इसका पुन: उपयोग किया जाएगा। जो वास्तव में मैं चाहता था।

वास्तव में यह बहुत लचीला है। ध्यान दें कि tsकॉलम सभी पक्षों पर पूरी तरह से कैसे गायब है - क्योंकि इसका एक DEFAULTमूल्य है, SQLite किसी भी मामले में सही काम करेगा, इसलिए मुझे इसकी देखभाल करने की आवश्यकता नहीं है।

आप दोनों newऔर oldपक्षों पर एक कॉलम भी शामिल कर सकते हैं और फिर COALESCE(new.content, old.content)बाहरी रूप SELECTसे उदाहरण के लिए उपयोग कर सकते हैं "नई सामग्री डालें यदि कोई था, अन्यथा पुरानी सामग्री रखें" - जैसे यदि आप एक निश्चित क्वेरी का उपयोग कर रहे हैं और नए को बाइंड कर रहे हैं प्लेसहोल्डर्स के साथ मूल्य।


13
+1, महान काम करता है, लेकिन चीजों को गति देने WHERE name = "about"के SELECT ... AS oldलिए एक बाधा जोड़ें । यदि आपके पास 1m + पंक्तियाँ हैं, तो यह बहुत धीमी है।

आपकी टिप्पणी पर अच्छा अंक, +1। हालांकि, मैं इस जवाब से बाहर निकल जाऊंगा, क्योंकि WHEREइस तरह के क्लॉज को जोड़ने के लिए क्वेरी में सिर्फ उस तरह की अतिरेक की आवश्यकता होती है, जब मैं इस दृष्टिकोण के साथ आने पर पहली जगह को कम करने की कोशिश कर रहा था। हमेशा की तरह: जब आपको प्रदर्शन की आवश्यकता होती है, तो असामान्यता - इस मामले में क्वेरी की संरचना।
अरस्तू पगलतज़िस

4
यदि आप चाहें तो आप अरस्तू के उदाहरण को सरल बना सकते हैं: INSERT OR REPLACE INTO page (id, name, title, content, author) SELECT id, 'about', 'About this site', content, 42 FROM ( SELECT NULL ) LEFT JOIN ( SELECT * FROM page WHERE name = 'about' )
15

4
क्या यह अनावश्यक रूप से ट्रिगर नहीं करेगा ON DELETEजब यह एक प्रतिस्थापन (जो एक अद्यतन है) करता है?
करकुरी

3
यह निश्चित रूप से ट्रिगर करेगा ON DELETE। दुनो बेवजह के बारे में। अधिकांश उपयोगकर्ताओं के लिए, यह संभवतः अनावश्यक, यहां तक ​​कि अवांछित होगा, लेकिन शायद सभी उपयोगकर्ताओं के लिए नहीं। इस तथ्य के लिए कि यह प्रश्न में पंक्ति में विदेशी कुंजी के साथ किसी भी पंक्तियों को कैस्केड-हटा देगा - शायद कई उपयोगकर्ताओं के लिए एक समस्या है। SQLite के पास वास्तविक UPSERT के करीब कुछ भी नहीं है, दुर्भाग्य से। (एक INSTEAD OF UPDATEट्रिगर के साथ इसे बचाने के लिए बचाओ , मुझे लगता है।)
अरस्तू पगलतज़िस

86

यदि आप आम तौर पर अपडेट कर रहे हैं तो मैं करूंगा।

  1. एक लेनदेन शुरू करें
  2. अद्यतन करें
  3. पंक्ति-पंक्ति की जाँच करें
  4. यदि यह 0 है तो इंसर्ट करें
  5. कमिट

यदि आप आम तौर पर आवेषण कर रहे हैं तो मैं करूंगा

  1. एक लेनदेन शुरू करें
  2. सम्मिलित करने का प्रयास करें
  3. प्राथमिक कुंजी उल्लंघन त्रुटि की जाँच करें
  4. अगर हमें एक त्रुटि मिली तो अपडेट करें
  5. कमिट

इस तरह से आप सेलेक्ट से बच जाते हैं और आप Sqlite पर ट्रांजैक्शनल साउंड करते हैं।


धन्यवाद, बस इस @ stackoverflow.com/questions/2717590/… से पूछा गया । मुझ से +1! =)
एलिक्स एक्सल

4
यदि आप 3rd चरण पर sqlite3_changes () का उपयोग करके राउटर चेक करने जा रहे हैं, तो सुनिश्चित करें कि आप संशोधनों के लिए कई थ्रेड्स में से DB हैंडल का उपयोग नहीं करते हैं।
लिनुलिन

3
निम्न समान प्रभाव के साथ अभी तक कम चिंताजनक नहीं होंगे: 1) आईडी फॉर्म तालिका का चयन करें जहां आईडी = 'x' 2) यदि (ResultSet.rows.length == 0) अपडेट तालिका जहां आईडी = 'x';
फ्लोरिन

20
मैं वास्तव में इच्छा या अद्यतन भाषा का हिस्सा था
फ्लोरिन

यह मेरे लिए सबसे अच्छा काम कर रहा है, REPLACE INTO करने की कोशिश करना या हमेशा सम्मिलित करना / अपडेट करना मेरे प्रदर्शन को मार रहा था जब मैंने मुख्य रूप से आवेषण के बजाय अपडेट किया था।
फेरिक ऑक्साइड

83

इस उत्तर को अद्यतन किया गया है और इसलिए नीचे दी गई टिप्पणियाँ अब लागू नहीं होती हैं।

2018-05-18 स्टॉप प्रेस।

SQLite में UPSERT का समर्थन! UPSERT सिंटैक्स को 3.24.0 (लंबित) संस्करण के साथ SQLite में जोड़ा गया था!

UPSERT INSERT के लिए एक विशेष वाक्यविन्यास जोड़ है जो INSERT को UPDATE या नो-ऑप के रूप में व्यवहार करने का कारण बनता है यदि INSERT एक अद्वितीय बाधा का उल्लंघन करता है। UPSERT मानक SQL नहीं है। SQLite में UPSERT, PostgreSQL द्वारा स्थापित सिंटैक्स का अनुसरण करता है।

यहाँ छवि विवरण दर्ज करें

वैकल्पिक रूप से:

ऐसा करने का एक और पूरी तरह से अलग तरीका है: मेरे आवेदन में मैंने अपनी मेमोरी की पंक्ति को लंबे समय तक सेट किया है। जब मैं मेमोरी में पंक्ति बनाता हूं तो मैक्सवैल्यू। (MaxValue को कभी भी आईडी के रूप में उपयोग नहीं किया जाएगा। आप लंबे समय तक नहीं रह पाएंगे। .... यदि पंक्ति का मान उस मूल्य का नहीं है, तो यह पहले से ही डेटाबेस में होना चाहिए, इसलिए इसे एक अद्यतन की आवश्यकता है यदि यह MaxValue है तो इसे सम्मिलित करने की आवश्यकता है। यह केवल तभी उपयोगी है जब आप अपने ऐप में पंक्ति-निर्धारण को ट्रैक कर सकते हैं।


5
तथास्तु। सरल जटिल से बेहतर है। यह स्वीकृत उत्तर की तुलना में थोड़ा सरल लगता है।
कोकुरियन

4
मैंने सोचा था कि आप INSERT INTO नहीं कर सकते ... साइक्लाइट में कहां? यह sqlite3 में मेरे लिए एक वाक्यविन्यास त्रुटि है
चार्ली मार्टिन

5
@CharlieMartin सही है। यह सिंटैक्स SQLite के लिए अमान्य है - जो कि ओपी ने अनुरोध किया है। एक WHEREखंड एक INSERTबयान में संलग्न नहीं किया जा सकता है :
sqlite-

4
इस जवाब ने मुझे बहुत ढीला कर दिया है, सवाल SQLITE के बारे में है और मुझे नहीं पता था कि INSERT WHERE को sqite में सपोर्ट नहीं किया गया है, कृपया इस नोट को जोड़ें कि यह आपके उत्तर में sqlite के लिए अमान्य है
Miquel

3
@colminator और अन्य: मैंने आपके सुझाव का उपयोग करते हुए उसका एसक्यूएल स्टेटमेंट तय किया।
F Lekschas

61

मुझे लगता है कि यह एक पुराना धागा है, लेकिन मैं sqlite3 में देर से काम कर रहा हूं और इस पद्धति के साथ आया हूं जो गतिशील रूप से उत्पन्न किए गए प्रश्नों की मेरी आवश्यकताओं को बेहतर रूप से अनुकूल करता है:

insert or ignore into <table>(<primaryKey>, <column1>, <column2>, ...) values(<primaryKeyValue>, <value1>, <value2>, ...); 
update <table> set <column1>=<value1>, <column2>=<value2>, ... where changes()=0 and <primaryKey>=<primaryKeyValue>; 

यह अभी भी 2 क्वेरी है जहां अपडेट पर क्लॉज़ है लेकिन लगता है कि यह ट्रिक है। मेरे दिमाग में भी यह दृष्टि है कि साइक्लाइट अपडेट स्टेटमेंट को पूरी तरह से ऑप्टिमाइज़ कर सकता है अगर कॉल टू चेंज () शून्य से अधिक हो। यह वास्तव में मेरे ज्ञान से परे है या नहीं, लेकिन एक आदमी वह सपना देख सकता है या नहीं? ;)

बोनस अंकों के लिए आप इस पंक्ति को जोड़ सकते हैं जो आपको पंक्ति की आईडी लौटाती है चाहे वह नई सम्मिलित पंक्ति हो या मौजूदा पंक्ति।

select case changes() WHEN 0 THEN last_insert_rowid() else <primaryKeyValue> end;

+ 1. बिल्कुल क्या मैं कुछ SQL सर्वर TSQL कोड से रूपांतरण करने की कोशिश कर रहा था। धन्यवाद। एसक्यूएल I को बदलने की कोशिश की जा रही थीUpdate <statement> If @@ROWCOUNT=0 INSERT INTO <statement>
डैरेन एमबी

14

यहां एक समाधान है जो वास्तव में एक INSERT या REPLACE (जो कई स्थितियों में अलग-अलग काम करता है) के बजाय एक UPSERT (UPDATE या INSERT) है।

यह इस तरह से काम करता है:
1. यदि एक ही आईडी के साथ एक रिकॉर्ड मौजूद है, तो अपडेट करने का प्रयास करें।
2. यदि अद्यतन ने कोई पंक्तियाँ नहीं बदलीं ( NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0)), तो रिकॉर्ड डालें।

तो या तो एक मौजूदा रिकॉर्ड अपडेट किया गया था या एक इंसर्ट किया जाएगा।

महत्वपूर्ण विवरण परिवर्तनों का उपयोग करने के लिए है () SQL फ़ंक्शन यह जांचने के लिए कि क्या अपडेट स्टेटमेंट किसी भी मौजूदा रिकॉर्ड को हिट करता है और केवल इन्सर्ट स्टेटमेंट करता है अगर उसने कोई रिकॉर्ड नहीं मारा है।

उल्लेख करने के लिए एक बात यह है कि परिवर्तन () फ़ंक्शन निचले स्तर के ट्रिगर्स ( http://sqlite.org/lang_corefunc.html#changes ) द्वारा किए गए परिवर्तनों को वापस नहीं करता है , इसलिए इसे ध्यान में रखना सुनिश्चित करें।

यहाँ एसक्यूएल है ...

टेस्ट अपडेट:

--Create sample table and records (and drop the table if it already exists)
DROP TABLE IF EXISTS Contact;
CREATE TABLE [Contact] (
  [Id] INTEGER PRIMARY KEY, 
  [Name] TEXT
);
INSERT INTO Contact (Id, Name) VALUES (1, 'Mike');
INSERT INTO Contact (Id, Name) VALUES (2, 'John');

-- Try to update an existing record
UPDATE Contact
SET Name = 'Bob'
WHERE Id = 2;

-- If no record was changed by the update (meaning no record with the same Id existed), insert the record
INSERT INTO Contact (Id, Name)
SELECT 2, 'Bob'
WHERE NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0);

--See the result
SELECT * FROM Contact;

टेस्ट डालें:

--Create sample table and records (and drop the table if it already exists)
DROP TABLE IF EXISTS Contact;
CREATE TABLE [Contact] (
  [Id] INTEGER PRIMARY KEY, 
  [Name] TEXT
);
INSERT INTO Contact (Id, Name) VALUES (1, 'Mike');
INSERT INTO Contact (Id, Name) VALUES (2, 'John');

-- Try to update an existing record
UPDATE Contact
SET Name = 'Bob'
WHERE Id = 3;

-- If no record was changed by the update (meaning no record with the same Id existed), insert the record
INSERT INTO Contact (Id, Name)
SELECT 3, 'Bob'
WHERE NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0);

--See the result
SELECT * FROM Contact;

2
एरिक लोगों की तुलना में यह मेरे लिए बेहतर समाधान है। हालांकि INSERT INTO Contact (Id, Name) SELECT 3, 'Bob' WHERE changes() = 0;काम भी करना चाहिए।
bkausbk

शुक्रिया यार, यह WebSQL (कॉर्डोवा और SQLite प्लगइन का उपयोग करके) में भी काम करता है
Zappescu

11

संस्करण 3.24.0 UPSERT के साथ शुरुआत करना SQLite द्वारा समर्थित है।

से प्रलेखन :

UPSERT INSERT के लिए एक विशेष वाक्यविन्यास जोड़ है जो INSERT को एक अद्यतन के रूप में व्यवहार करने का कारण बनता है या यदि कोई INSERT एक अद्वितीय बाधा का उल्लंघन करता है। UPSERT मानक SQL नहीं है। SQLERT में UPSERT, PostgreSQL द्वारा स्थापित सिंटैक्स का अनुसरण करता है। UPSERT सिंटैक्स को 3.24.0 (लंबित) संस्करण के साथ SQLite में जोड़ा गया था।

एक यूपीएसईआरटी एक सामान्य INSERT स्टेटमेंट है, जिसके बाद विशेष ON CONFLICT क्लॉज है

यहाँ छवि विवरण दर्ज करें

छवि स्रोत: https://www.sqlite.org/images/syntax/upsert-clause.gif


8

आप वास्तव में SQLite में एक upsert कर सकते हैं, यह सिर्फ एक अलग लग रहा है की तुलना में आप के लिए उपयोग किया जाता है। यह कुछ इस तरह दिखेगा:

INSERT INTO table name (column1, column2) 
VALUES ("value12", "value2") WHERE id = 123 
ON CONFLICT DO UPDATE 
SET column1 = "value1", column2 = "value2" WHERE id = 123

5

अरस्तू के जवाब पर विस्तार करते हुए आप एक डमी 'सिंगलटन' टेबल (एकल पंक्ति के साथ अपनी रचना की एक तालिका) से चयन कर सकते हैं। यह कुछ दोहराव से बचा जाता है।

मैंने MySQL और SQLite में उदाहरण को पोर्टेबल रखा है और उदाहरण के तौर पर 'date_added' कॉलम का उपयोग किया है कि आप पहली बार केवल एक कॉलम कैसे सेट कर सकते हैं।

 REPLACE INTO page (
   id,
   name,
   title,
   content,
   author,
   date_added)
 SELECT
   old.id,
   "about",
   "About this site",
   old.content,
   42,
   IFNULL(old.date_added,"21/05/2013")
 FROM singleton
 LEFT JOIN page AS old ON old.name = "about";

4

मुझे पता है कि सबसे अच्छा तरीका एक अद्यतन करना है, इसके बाद सम्मिलित करें। "एक चयन की भूमि के ऊपर" के लिए आवश्यक है, लेकिन यह जब से तुम प्राथमिक कुंजी है, जो तेजी से होता है पर खोज कर रहे एक भयानक बोझ नहीं है।

आपको अपनी तालिका और फ़ील्ड नामों के साथ नीचे दिए गए कथनों को संशोधित करने में सक्षम होना चाहिए कि आप क्या चाहते हैं।

--first, update any matches
UPDATE DESTINATION_TABLE DT
SET
  MY_FIELD1 = (
              SELECT MY_FIELD1
              FROM SOURCE_TABLE ST
              WHERE ST.PRIMARY_KEY = DT.PRIMARY_KEY
              )
 ,MY_FIELD2 = (
              SELECT MY_FIELD2
              FROM SOURCE_TABLE ST
              WHERE ST.PRIMARY_KEY = DT.PRIMARY_KEY
              )
WHERE EXISTS(
            SELECT ST2.PRIMARY_KEY
            FROM
              SOURCE_TABLE ST2
             ,DESTINATION_TABLE DT2
            WHERE ST2.PRIMARY_KEY = DT2.PRIMARY_KEY
            );

--second, insert any non-matches
INSERT INTO DESTINATION_TABLE(
  MY_FIELD1
 ,MY_FIELD2
)
SELECT
  ST.MY_FIELD1
 ,NULL AS MY_FIELD2  --insert NULL into this field
FROM
  SOURCE_TABLE ST
WHERE NOT EXISTS(
                SELECT DT2.PRIMARY_KEY
                FROM DESTINATION_TABLE DT2
                WHERE DT2.PRIMARY_KEY = ST.PRIMARY_KEY
                );

7
जटिल का रास्ता।
frast

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

3

यदि कोई कॉर्डोवा में SQLite के लिए मेरे समाधान को पढ़ना चाहता है, तो मुझे @ सामान्य उत्तर के लिए यह सामान्य जेएस विधि धन्यवाद मिला।

function    addOrUpdateRecords(tableName, values, callback) {
get_columnNames(tableName, function (data) {
    var columnNames = data;
    myDb.transaction(function (transaction) {
        var query_update = "";
        var query_insert = "";
        var update_string = "UPDATE " + tableName + " SET ";
        var insert_string = "INSERT INTO " + tableName + " SELECT ";
        myDb.transaction(function (transaction) {
            // Data from the array [[data1, ... datan],[()],[()]...]:
            $.each(values, function (index1, value1) {
                var sel_str = "";
                var upd_str = "";
                var remoteid = "";
                $.each(value1, function (index2, value2) {
                    if (index2 == 0) remoteid = value2;
                    upd_str = upd_str + columnNames[index2] + "='" + value2 + "', ";
                    sel_str = sel_str + "'" + value2 + "', ";
                });
                sel_str = sel_str.substr(0, sel_str.length - 2);
                sel_str = sel_str + " WHERE NOT EXISTS(SELECT changes() AS change FROM "+tableName+" WHERE change <> 0);";
                upd_str = upd_str.substr(0, upd_str.length - 2);
                upd_str = upd_str + " WHERE remoteid = '" + remoteid + "';";                    
                query_update = update_string + upd_str;
                query_insert = insert_string + sel_str;  
                // Start transaction:
                transaction.executeSql(query_update);
                transaction.executeSql(query_insert);                    
            });
        }, function (error) {
            callback("Error: " + error);
        }, function () {
            callback("Success");
        });
    });
});
}

तो, पहले इस फ़ंक्शन के साथ कॉलम नाम चुनें:

function get_columnNames(tableName, callback) {
myDb.transaction(function (transaction) {
    var query_exec = "SELECT name, sql FROM sqlite_master WHERE type='table' AND name ='" + tableName + "'";
    transaction.executeSql(query_exec, [], function (tx, results) {
        var columnParts = results.rows.item(0).sql.replace(/^[^\(]+\(([^\)]+)\)/g, '$1').split(','); ///// RegEx
        var columnNames = [];
        for (i in columnParts) {
            if (typeof columnParts[i] === 'string')
                columnNames.push(columnParts[i].split(" ")[0]);
        };
        callback(columnNames);
    });
});
}

फिर लेनदेन को प्रोग्रामेटिक रूप से करें।

"मान" एक ऐसी सरणी है जिसे आपको पहले बनाना चाहिए और यह उन पंक्तियों का प्रतिनिधित्व करती है जिन्हें आप तालिका में सम्मिलित करना या अपडेट करना चाहते हैं।

"रिमोटिड" वह आईडी है जिसका उपयोग मैंने एक संदर्भ के रूप में किया है, क्योंकि मैं अपने रिमोट सर्वर के साथ सिंक कर रहा हूं।

SQLite कॉर्डोवा प्लगइन के उपयोग के लिए, कृपया आधिकारिक लिंक देखें


2

यह विधि इस प्रश्न के उत्तर में से कुछ अन्य तरीकों को याद करती है और CTE (कॉमन टेबल एक्सप्रेशंस) के उपयोग को शामिल करती है। मैं प्रश्न का परिचय दूंगा फिर समझाऊंगा कि मैंने जो किया वह क्यों किया।

यदि कर्मचारी 300 है तो मैं कर्मचारी नाम के अंतिम नाम को डीएवीआईएस में बदलना चाहता हूं। अन्यथा, मैं एक नया कर्मचारी जोड़ूंगा।

तालिका का नाम: कर्मचारी कॉलम: आईडी, first_name, last_name

क्वेरी है:

INSERT OR REPLACE INTO employees (employee_id, first_name, last_name)
WITH registered_employees AS ( --CTE for checking if the row exists or not
    SELECT --this is needed to ensure that the null row comes second
        *
    FROM (
        SELECT --an existing row
            *
        FROM
            employees
        WHERE
            employee_id = '300'

        UNION

        SELECT --a dummy row if the original cannot be found
            NULL AS employee_id,
            NULL AS first_name,
            NULL AS last_name
    )
    ORDER BY
        employee_id IS NULL --we want nulls to be last
    LIMIT 1 --we only want one row from this statement
)
SELECT --this is where you provide defaults for what you would like to insert
    registered_employees.employee_id, --if this is null the SQLite default will be used
    COALESCE(registered_employees.first_name, 'SALLY'),
    'DAVIS'
FROM
    registered_employees
;

मूल रूप से, मैंने डिफ़ॉल्ट मान निर्धारित करने के लिए सेलेक्ट स्टेटमेंट का उपयोग करने के लिए सीटीई का उपयोग किया है। चूंकि यह एक सीटीई है, हम केवल तालिका से इच्छित कॉलम का चयन करते हैं और INSERT स्टेटमेंट इसका उपयोग करता है।

अब आप तय कर सकते हैं कि नल क्या बदलकर उपयोग करना चाहते हैं, क्या मान हो सकते हैं।


2

बाद अरस्तू Pagaltzis और के विचार COALESCEसे एरिक बी का जवाब है, यहाँ यह केवल कुछ स्तंभों को अपडेट करने या पूर्ण पंक्ति सम्मिलित करता है, तो यह मौजूद नहीं है के लिए एक Upsert विकल्प है।

इस मामले में, कल्पना करें कि शीर्षक और सामग्री को अपडेट किया जाना चाहिए, अन्य पुराने मूल्यों को ध्यान में रखते हुए जब मौजूदा और आपूर्ति किए गए सम्मिलित करते हैं जब नाम नहीं मिलता है:

नोट id को निरस्त होने के लिए मजबूर किया INSERTजाता है, क्योंकि इसे ऑटोइन्क्रिमेंट माना जाता है। यदि यह केवल एक उत्पन्न प्राथमिक कुंजी है तो COALESCEइसका उपयोग भी किया जा सकता है ( अरस्तू पगलतज़िस की टिप्पणी देखें )।

WITH new (id, name, title, content, author)
     AS ( VALUES(100, 'about', 'About this site', 'Whatever new content here', 42) )
INSERT OR REPLACE INTO page (id, name, title, content, author)
SELECT
     old.id, COALESCE(old.name, new.name),
     new.title, new.content,
     COALESCE(old.author, new.author)
FROM new LEFT JOIN page AS old ON new.name = old.name;

तो सामान्य नियम यह होगा, यदि आप पुराने मान रखना चाहते हैं, उपयोग करें COALESCE, जब आप मानों को अद्यतन करना चाहते हैं, का उपयोग करेंnew.fieldname


COALESCE(old.id, new.id)निश्चित रूप से एक स्वत: अंकन कुंजी के साथ गलत है। और जबकि "अधिकांश पंक्ति को अपरिवर्तित रखें, जहां मान गायब हैं," का उपयोग ऐसा लगता है जैसे किसी व्यक्ति के पास वास्तव में हो सकता है, मुझे नहीं लगता है कि लोग उस समय की तलाश कर रहे हैं जब वे एक यूपीएसईआरटी कैसे करना चाहते हैं।
अरस्तू पगलतज़िस

@AristotlePagaltzis माफी माँगता हूँ अगर मैं गलत हूँ, मैं autoincrements का उपयोग नहीं कर रहा हूँ। इस क्वेरी के साथ जो मैं देख रहा हूं, वह केवल कुछ ही कॉल को अपडेट करने के लिए है या आपूर्ति की गई वैल्यू के साथ पूर्ण पंक्ति सम्मिलित करने के मामले में यह मौजूद नहीं है । मैं आपकी क्वेरी के साथ खेल रहा हूं और सम्मिलित करते समय मैं इसे प्राप्त नहीं कर सका: oldतालिका में चयनित कॉलम जहां निर्दिष्ट किए गए NULLमानों में नहीं, को सौंपा गया है new। यह उपयोग करने का कारण है COALESCE। मैं sqlite का विशेषज्ञ नहीं हूं, मैं इस प्रश्न का परीक्षण कर रहा हूं और इस मामले के लिए काम करता हूं, मैं आपको बहुत धन्यवाद दूंगा अगर आप मुझे ऑटोइन्क्रिमेंट्स के साथ समाधान के लिए इंगित कर सकते हैं
Miquel

2
स्वत: अंकन कुंजी के मामले में, आप कुंजी के रूप में सम्मिलित करना चाहते हैंNULL , क्योंकि यह SQLite को इसके बजाय अगले उपलब्ध मान को सम्मिलित करने के लिए कहता है।
अरस्तू पगलतज़िस

मैं यह नहीं कह रहा हूं कि आपको वह नहीं करना चाहिए जो आप कर रहे हैं (यदि आपको इसकी आवश्यकता है तो आपको इसकी आवश्यकता है), बस यह वास्तव में यहां सवाल का जवाब नहीं है। यूपीएसईआरटी का आम तौर पर मतलब है कि आपके पास एक पंक्ति है जिसे आप तालिका में संग्रहीत करना चाहते हैं और बस यह नहीं जानते कि आपके पास मूल्यों को डालने के लिए पहले से ही एक मिलान पंक्ति है या उन्हें एक नई पंक्ति के रूप में सम्मिलित करने की आवश्यकता है। आपका उपयोग मामला यह है कि यदि आपके पास पहले से ही एक मिलान पंक्ति है, तो आप नई पंक्ति से अधिकांश मानों को अनदेखा करना चाहते हैं । यह ठीक है, यह सिर्फ सवाल नहीं है जो पूछा गया था।
अरस्तू पगलतज़िस

1

मुझे लगता है कि यह वही हो सकता है जो आप ढूंढ रहे हैं: ON CONFLICT क्लॉज

यदि आप अपनी तालिका को इस तरह परिभाषित करते हैं:

CREATE TABLE table1( 
    id INTEGER PRIMARY KEY ON CONFLICT REPLACE, 
    field1 TEXT 
); 

अब, यदि आप पहले से मौजूद आईडी के साथ एक INSERT करते हैं, तो SQLite स्वचालित रूप से INSERT के बजाय UPDATE करता है।

HTH ...


6
मुझे नहीं लगता कि यह काम करता है, यह सम्मिलित विवरण से गायब कॉलम मिटा देगा
सैम केसर

3
@ मेसर: -1 मुझसे, सॉरी। यह एक REPLACEबयान जारी करने के समान है ।
एलिक्स एक्सल

7
-1 क्योंकि यह डिलीट करता है और फिर एक इन्सर्ट करता है अगर प्राइमरी की पहले से मौजूद है।
frast

0

यदि आपको दो ऑपरेशन में ऐसा करने में कोई आपत्ति नहीं है।

कदम:

1) "INSERT OR IGNORE" के साथ नए आइटम जोड़ें

2) अद्यतन आइटम "अद्यतन" के साथ

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

ज़रूर, धीमा, आदि। हां।

एसक्यूएल लिखना और इसे बनाए रखना और समझना आसान है? निश्चित रूप से।

इस पर विचार करना एक व्यापार है। छोटे upserts के लिए महान काम करता है। उन लोगों के लिए महान काम करता है जो कोड रखरखाव के लिए दक्षता का त्याग करने में कोई आपत्ति नहीं करते हैं।


सभी के लिए ध्यान दें: INSERT या REPLACE इस पोस्ट के बारे में नहीं है। INSERT या REPLACE एक नई ID के साथ, आपकी तालिका में एक नई पंक्ति बनाएगा। यह एक अद्यतन नहीं है।
सापबकेट

-2

इस धागे को पढ़ने के बाद और निराश हो गए कि यह "यूपीएसईआरटी" आईएनजी के लिए आसान नहीं था, मैंने आगे की जांच की ...

आप वास्तव में सीधे और आसानी से SQLITE में ऐसा कर सकते हैं।

के बजाय का उपयोग करने का: INSERT INTO

उपयोग: INSERT OR REPLACE INTO

यह वही करता है जो आप इसे करना चाहते हैं!


21
-1 INSERT OR REPLACEनहीं है UPSERT। कारण के लिए gregschlom का "उत्तर" देखें । एरिक बी का समाधान वास्तव में काम करता है और कुछ upvotes की आवश्यकता है।
दिन

-4
SELECT COUNT(*) FROM table1 WHERE id = 1;

अगर COUNT(*) = 0

INSERT INTO table1(col1, col2, cole) VALUES(var1,var2,var3);

और अगर COUNT(*) > 0

UPDATE table1 SET col1 = var4, col2 = var5, col3 = var6 WHERE id = 1;

यह बहुत जटिल है, SQL इसे केवल एक क्वेरी में ठीक कर सकता है
रॉबिन कैंटर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.