SQLite UPSERT / अद्यतन या इन्सर्ट


102

मुझे एक SQLite डेटाबेस के खिलाफ UPSERT / INSERT या UPDATE करने की आवश्यकता है।

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

यह तालिका होगी:

खिलाड़ी - (आईडी पर प्राथमिक कुंजी, user_name अद्वितीय)

|  id   | user_name |  age   |
------------------------------
|  1982 |   johnny  |  23    |
|  1983 |   steven  |  29    |
|  1984 |   pepee   |  40    |

जवाबों:


51

यह एक देर से जवाब है। 4 जून, 2018 को रिलीज़ SQLIte 3.24.0 से शुरू, अंत में PostgreSQL सिंटैक्स के बाद UPSERT क्लॉज का समर्थन है ।

INSERT INTO players (user_name, age)
  VALUES('steven', 32) 
  ON CONFLICT(user_name) 
  DO UPDATE SET age=excluded.age;

नोट: 3.24.0 से पहले SQLite के एक संस्करण का उपयोग करने वाले लोगों के लिए, कृपया नीचे इस उत्तर (मेरे द्वारा पोस्ट किया गया, @MarqueIV) को देखें।

हालाँकि, यदि आपके पास अपग्रेड करने का विकल्प है, तो आपको मेरे समाधान के विपरीत ऐसा करने के लिए दृढ़ता से प्रोत्साहित किया जाता है, यहाँ पोस्ट किया गया एक ही कथन में वांछित व्यवहार को प्राप्त करता है। साथ ही आपको अन्य सभी सुविधाएं, सुधार और बग फिक्स मिलते हैं जो आमतौर पर अधिक हालिया रिलीज के साथ आते हैं।


अभी के लिए, उबंटू रिपॉजिटरी में अभी तक यह रिलीज नहीं हुई है।
bl79

मैं Android पर इसका उपयोग क्यों नहीं कर सकता? मैंने कोशिश की db.execSQL("insert into bla(id,name) values (?,?) on conflict(id) do update set name=?")। मुझे "ऑन" शब्द पर एक सिंटैक्स त्रुटि देता है
बैस्टियन वोइगट

1
@BastianVoigt क्योंकि Android के विभिन्न संस्करणों पर स्थापित SQLite3 पुस्तकालय 3.24.0 से पुराने हैं। देखें: developer.android.com/reference/android/database/sqlite/… अफसोस की बात यह है कि आपको Android या iOS पर SQLite3 (या किसी अन्य सिस्टम लाइब्रेरी) की एक नई सुविधा की आवश्यकता है, आपको अपने में SQLite का एक विशिष्ट संस्करण बंडल करने की आवश्यकता है एक स्थापित सिस्टम पर भरोसा करने के बजाय आवेदन।
प्रॉपिन

UPSERT के बजाय, यह एक INDATE से अधिक नहीं है क्योंकि यह पहले डालने की कोशिश करता है? ;)
मार्क ए। डोनोहे

@BastianVoigt, कृपया नीचे मेरा उत्तर देखें (उपरोक्त प्रश्न में जुड़ा हुआ) जो 3.24.0 से पहले के संस्करणों के लिए है।
मार्क ए। डोनोहे

105

क्यू एंड ए स्टाइल

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


विकल्प 1: आप पंक्ति को हटा सकते हैं

दूसरे शब्दों में, आपके पास विदेशी कुंजी नहीं है, या यदि आपके पास है, तो आपका SQLite इंजन कॉन्फ़िगर किया गया है ताकि कोई अखंडता अपवाद न हों। जाने का रास्ता INSERT या REPLACE है । यदि आप ऐसे खिलाड़ी को सम्मिलित / अद्यतन करने का प्रयास कर रहे हैं, जिसकी आईडी पहले से मौजूद है, तो SQLite इंजन उस पंक्ति को हटा देगा और आपके द्वारा प्रदत्त डेटा सम्मिलित करेगा। अब सवाल आता है: पुरानी आईडी को संबद्ध रखने के लिए क्या करें?

मान लें कि हम डेटा उपयोगकर्ता_नाम = 'स्टीवन' और आयु = 32 के साथ यूपीएसईआरटी करना चाहते हैं ।

इस कोड को देखें:

INSERT INTO players (id, name, age)

VALUES (
    coalesce((select id from players where user_name='steven'),
             (select max(id) from drawings) + 1),
    32)

चाल तालमेल में है। यह उपयोगकर्ता 'स्टीवन' की आईडी लौटाता है यदि कोई हो, और अन्यथा, यह एक नई ताज़ा आईडी लौटाता है।


विकल्प 2: आप पंक्ति को हटा नहीं सकते

पिछले समाधान के साथ घूमने के बाद, मुझे एहसास हुआ कि मेरे मामले में जो डेटा को नष्ट कर सकता है, क्योंकि यह आईडी अन्य तालिका के लिए एक विदेशी कुंजी के रूप में काम करती है। इसके अलावा, मैंने टेबल को DELETE CASCADE पर क्लॉज़ के साथ बनाया , जिसका मतलब होगा कि यह डेटा को चुपचाप हटा देगा। खतरनाक।

इसलिए, मैंने पहले एक IF क्लॉज के बारे में सोचा, लेकिन SQLite में केवल CASE है । और इस मामले का उपयोग नहीं किया जा सकता है (या कम से कम मैंने इसे प्रबंधित नहीं किया था) एक अद्यतन क्वेरी करने के लिए यदि EXISTS (खिलाड़ियों से आईडी का चयन करें जहां user_name = 'स्टीवन'), और INSERT यदि ऐसा नहीं हुआ। नही जाओ।

और फिर, आखिरकार मैंने सफलता के साथ, जानवर बल का उपयोग किया। तर्क यह है कि प्रत्येक यूपीएसईआरटी के लिए जिसे आप प्रदर्शन करना चाहते हैं, पहले यह सुनिश्चित करने के लिए INSERT या IGNORE को निष्पादित करें कि हमारे उपयोगकर्ता के साथ कोई पंक्ति है, और फिर उसी डेटा के साथ एक UPDATE क्वेरी निष्पादित करें जिसे आपने सम्मिलित करने का प्रयास किया था।

पहले जैसा ही डेटा: user_name = 'स्टीवन' और उम्र = 32।

-- make sure it exists
INSERT OR IGNORE INTO players (user_name, age) VALUES ('steven', 32); 

-- make sure it has the right data
UPDATE players SET user_name='steven', age=32 WHERE user_name='steven'; 

और बस यही!

संपादित करें

जैसा कि एंडी ने टिप्पणी की है, पहले डालने की कोशिश कर रहा है और फिर अपडेट से फायरिंग ट्रिगर हो सकता है जो अपेक्षा से अधिक बार होता है। यह मेरी राय में डेटा सुरक्षा का मुद्दा नहीं है, लेकिन यह सच है कि अनावश्यक घटनाओं को अंजाम देना थोड़ा कम मायने रखता है। इसलिए, एक बेहतर समाधान होगा:

-- Try to update any existing row
UPDATE players SET age=32 WHERE user_name='steven';

-- Make sure it exists
INSERT OR IGNORE INTO players (user_name, age) VALUES ('steven', 32); 

10
Ditto ... विकल्प 2 बढ़िया है। सिवाय इसके, मैंने इसे दूसरी तरह से किया: एक अद्यतन का प्रयास करें, जांचें कि क्या पंक्तियाँ> 0, यदि नहीं तो सम्मिलित करें।
टॉम स्पेंसर

यह एक बहुत अच्छा दृष्टिकोण है, केवल छोटी खामी यह है कि आपके पास "upsert" के लिए केवल एक SQL नहीं है।
जुगसच

2
आपको अंतिम कोड नमूने में अपडेट स्टेटमेंट में user_name को फिर से सेट करने की आवश्यकता नहीं है। यह उम्र निर्धारित करने के लिए पर्याप्त है।
सर्ज स्टेटस

72

यहाँ एक दृष्टिकोण है कि ब्रूट-फोर्स 'इग्नोर' की आवश्यकता नहीं है, जो केवल तभी काम करेगा जब कोई महत्वपूर्ण उल्लंघन हो। यह तरीका आपके द्वारा अपडेट में निर्दिष्ट किसी भी स्थिति के आधार पर काम करता है ।

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

-- Try to update any existing row
UPDATE players
SET age=32
WHERE user_name='steven';

-- If no update happened (i.e. the row didn't exist) then insert one
INSERT INTO players (user_name, age)
SELECT 'steven', 32
WHERE (Select Changes() = 0);

यह काम किस प्रकार करता है

यहां 'मैजिक सॉस' का उपयोग क्लॉज Changes()में किया जा रहा Whereहै। Changes()अंतिम ऑपरेशन से प्रभावित पंक्तियों की संख्या का प्रतिनिधित्व करता है, जो इस मामले में अद्यतन है।

उपरोक्त उदाहरण में, यदि अपडेट से कोई बदलाव नहीं हुआ है (यानी रिकॉर्ड मौजूद नहीं है) तो Changes()= 0 इसलिए स्टेटमेंट Whereमें क्लॉज़ Insertसही का मूल्यांकन करता है और निर्दिष्ट डेटा के साथ एक नई पंक्ति डाली जाती है।

यदि किसी मौजूदा पंक्ति Update को अद्यतन किया गया है , तो Changes()= 1 (या अधिक सटीक, शून्य नहीं यदि एक से अधिक पंक्ति अद्यतन की गई थी), इसलिए अब 'कहाँ' खंड Insertगलत का मूल्यांकन करता है और इस प्रकार कोई आवेषण नहीं होगा।

इस की सुंदरता वहाँ कोई क्रूर बल की जरूरत है, और न ही अनावश्यक रूप से हटाने, तो फिर से डालने डेटा है जो विदेशी कुंजी रिश्तों में डाउनस्ट्रीम कुंजी को गड़बड़ कर सकता है।

इसके अतिरिक्त, चूंकि यह सिर्फ एक मानक Whereक्लॉज है, यह आपके द्वारा परिभाषित किसी भी चीज पर आधारित हो सकता है, न कि केवल मुख्य उल्लंघनों पर। इसी तरह, आप Changes()कुछ भी आप चाहते हैं के साथ संयोजन में उपयोग कर सकते हैं / कहीं भी अभिव्यक्ति की अनुमति की आवश्यकता है।


1
यह मेरे लिए बहुत अच्छा काम किया। मैंने इस समाधान को सभी INSERT या REPLACE उदाहरणों के साथ कहीं और नहीं देखा है, यह मेरे उपयोग के मामले के लिए बहुत अधिक लचीला है।
csab

@MarqueIV और क्या होगा अगर दो आइटम अपडेट किए जाएं या डाले जाएं? उदाहरण के लिए, फ़िर को अपडेट किया गया था, और दूसरा मौजूद नहीं था। इस तरह के मामले Changes() = 0में झूठी और दो पंक्तियाँ INSERT या REPLACE करेंगे
Andriy Antonov

आमतौर पर एक यूपीएसईआरटी एक रिकॉर्ड पर कार्रवाई करने वाला होता है। यदि आप कह रहे हैं कि आप यह सुनिश्चित करने के लिए जानते हैं कि यह एक से अधिक रिकॉर्ड पर काम कर रहा है, तो उसके अनुसार गणना की जांच को बदल दें।
मार्क ए। डोनोहे

बुरी बात यह है कि यदि पंक्ति मौजूद है, तो अद्यतन विधि निष्पादित होनी चाहिए, भले ही पंक्ति बदल गई हो या नहीं।
जिमि

1
वह बुरी चीज क्यों है? और अगर डेटा नहीं बदला है, तो आप UPSERTपहली जगह में क्यों बुला रहे हैं ? लेकिन फिर भी, यह एक अच्छी बात है कि अद्यतन होता है, सेटिंग Changes=1या अन्यथा INSERTकथन गलत तरीके से आग लगाएगा, जो आप इसे नहीं चाहते हैं।
मार्क ए। डोनोहो

25

सभी प्रस्तुत जवाबों के साथ समस्या यह ट्रिगर (और शायद अन्य दुष्प्रभावों) को खाते में लेने की पूरी कमी है। जैसे समाधान

INSERT OR IGNORE ...
UPDATE ...

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

उचित समाधान है

UPDATE OR IGNORE ...
INSERT OR IGNORE ...

उस स्थिति में केवल एक कथन निष्पादित किया जाता है (जब पंक्ति मौजूद है या नहीं)।


1
में तुम्हारी बात समझ रहा हूँ। मैं अपने सवाल को अपडेट करूंगा। वैसे, मुझे नहीं लगता कि क्यों UPDATE OR IGNOREजरूरी है, क्योंकि अगर कोई पंक्तियां नहीं मिलीं तो अपडेट क्रैश नहीं होगा।
bgusach

1
पठनीयता? मैं देख सकता हूं कि एंडी का कोड एक नज़र में क्या कर रहा है। तुम्हारा bgusach मुझे पता लगाने के लिए एक मिनट का अध्ययन करना था।
ब्रैंडन

6

कोई छेद (प्रोग्रामर के लिए) के साथ एक शुद्ध यूपीएसईआरटी होना चाहिए जो अद्वितीय और अन्य कुंजी पर रिले न करें:

UPDATE players SET user_name="gil", age=32 WHERE user_name='george'; 
SELECT changes();

चयनित परिवर्तन () अंतिम पूछताछ में किए गए अद्यतनों की संख्या लौटाएगा। फिर जांचें कि क्या परिवर्तनों से वापसी मूल्य () 0 है, यदि ऐसा निष्पादित हो:

INSERT INTO players (user_name, age) VALUES ('gil', 32); 

यह उसकी टिप्पणी में प्रस्तावित @fiznool के बराबर है (हालांकि मैं उसके समाधान के लिए जाऊंगा)। यह सब ठीक है और वास्तव में ठीक काम करता है, लेकिन आपके पास एक अद्वितीय SQL कथन नहीं है। UPSERT पीके या अन्य विशिष्ट कुंजियों पर आधारित नहीं है, इससे मुझे कोई मतलब नहीं है।
bgusach

4

आप केवल अपने उपयोगकर्ता_नाम की अद्वितीय बाधा पर ON CONFLICT REPLACE क्लॉज़ को भी जोड़ सकते हैं और फिर एक विवाद के मामले में क्या करना है, यह जानने के लिए इसे SQLite पर छोड़ दें। देखें: https://sqlite.org/lang_conflict.html

डिलीट ट्रिगर्स के बारे में भी वाक्य पर ध्यान दें: जब एक विरोधाभास को पूरा करने के लिए REPLACE संघर्ष रिज़ॉल्यूशन रणनीति पंक्तियों को हटाती है, तो ट्रिगर आग को हटा दें यदि और केवल तभी पुनरावर्ती ट्रिगर्स सक्षम हो।


1

विकल्प 1: डालें -> अपडेट करें

यदि आप दोनों से बचना पसंद करते हैं changes()=0और INSERT OR IGNOREभले ही आप पंक्ति को हटा नहीं सकते - आप इस तर्क का उपयोग कर सकते हैं;

सबसे पहले, डालें (यदि मौजूद नहीं है) और फिर अद्वितीय कुंजी के साथ फ़िल्टर करके अपडेट करें

उदाहरण

-- Table structure
CREATE TABLE players (
    id        INTEGER       PRIMARY KEY AUTOINCREMENT,
    user_name VARCHAR (255) NOT NULL
                            UNIQUE,
    age       INTEGER       NOT NULL
);

-- Insert if NOT exists
INSERT INTO players (user_name, age)
SELECT 'johnny', 20
WHERE NOT EXISTS (SELECT 1 FROM players WHERE user_name='johnny' AND age=20);

-- Update (will affect row, only if found)
-- no point to update user_name to 'johnny' since it's unique, and we filter by it as well
UPDATE players 
SET age=20 
WHERE user_name='johnny';

ट्रिगर के बारे में

सूचना: मैंने यह देखने के लिए परीक्षण नहीं किया है कि ट्रिगर किसे कहा जा रहा है, लेकिन मैं निम्नलिखित मानता हूं :

यदि पंक्ति मौजूद नहीं है

  • पहले से ही
  • INSTEAD OF का उपयोग कर INSERT
  • INSERT के बाद
  • आगे बढ़ें
  • अद्यतन का उपयोग कर अद्यतन
  • अद्यतन के बाद

यदि पंक्ति मौजूद है

  • आगे बढ़ें
  • अद्यतन का उपयोग कर अद्यतन
  • अद्यतन के बाद

विकल्प 2: सम्मिलित करें या बदलें - अपनी खुद की आईडी रखें

इस तरह से आपके पास एक एसक्यूएल कमांड हो सकता है

-- Table structure
CREATE TABLE players (
    id        INTEGER       PRIMARY KEY AUTOINCREMENT,
    user_name VARCHAR (255) NOT NULL
                            UNIQUE,
    age       INTEGER       NOT NULL
);

-- Single command to insert or update
INSERT OR REPLACE INTO players 
(id, user_name, age) 
VALUES ((SELECT id from players WHERE user_name='johnny' AND age=20),
        'johnny',
        20);

संपादित करें: जोड़ा गया विकल्प 2।

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