NULL मान के साथ PostgreSQL UPSERT मुद्दा


13

मैं पोस्टग्रेज 9.5 में नए यूपीएसईआरटी सुविधा का उपयोग करने के साथ एक समस्या है

मेरे पास एक तालिका है जिसका उपयोग किसी अन्य तालिका से डेटा एकत्र करने के लिए किया जाता है। मिश्रित कुंजी 20 स्तंभों से बना है, जिनमें से 10 अशक्त हो सकते हैं। नीचे मैंने विशेष रूप से NULL मान के साथ समस्या का एक छोटा संस्करण बनाया है।

CREATE TABLE public.test_upsert (
upsert_id serial,
name character varying(32) NOT NULL,
status integer NOT NULL,
test_field text,
identifier character varying(255),
count integer,
CONSTRAINT upsert_id_pkey PRIMARY KEY (upsert_id),
CONSTRAINT test_upsert_name_status_test_field_key UNIQUE (name, status, test_field)
);

इस क्वेरी को चलाने की आवश्यकता के अनुसार काम करता है (पहले सम्मिलित करें, फिर बाद में आवेषण केवल गिनती बढ़ाएँ):

INSERT INTO test_upsert as tu(name,status,test_field,identifier, count) 
VALUES ('shaun',1,'test value','ident', 1)
ON CONFLICT (name,status,test_field) DO UPDATE set count = tu.count + 1 
where tu.name = 'shaun' AND tu.status = 1 AND tu.test_field = 'test value';

हालाँकि अगर मैं इस क्वेरी को चलाता हूं, तो शुरुआती पंक्ति के लिए गिनती बढ़ाने के बजाय हर बार 1 पंक्ति डाली जाती है:

INSERT INTO test_upsert as tu(name,status,test_field,identifier, count) 
VALUES ('shaun',1,null,'ident', 1)
ON CONFLICT (name,status,test_field) DO UPDATE set count = tu.count + 1  
where tu.name = 'shaun' AND tu.status = 1 AND tu.test_field = null;

यह मेरा मुद्दा है। मुझे केवल गणना मूल्य बढ़ाने और शून्य मानों के साथ कई समान पंक्तियाँ बनाने की आवश्यकता नहीं है।

आंशिक अनन्य अनुक्रमणिका जोड़ने का प्रयास:

CREATE UNIQUE INDEX test_upsert_upsert_id_idx
ON public.test_upsert
USING btree
(name COLLATE pg_catalog."default", status, test_field, identifier);

हालाँकि, यह एक ही परिणाम देता है, या तो कई नल पंक्तियाँ डाली जा रही हैं या सम्मिलित करने का प्रयास करते समय यह त्रुटि संदेश:

त्रुटि: ON CONFLICT विनिर्देशन से मेल खाने वाला कोई अनूठा या बहिष्करण बाधा नहीं है

मैंने पहले ही आंशिक सूचकांक जैसे कि अतिरिक्त विवरण जोड़ने का प्रयास किया WHERE test_field is not null OR identifier is not null। हालाँकि, सम्मिलित करते समय मुझे बाधा त्रुटि संदेश मिलता है।

जवाबों:


15

ON CONFLICT DO UPDATEव्यवहार को स्पष्ट करें

यहां मैनुअल पर विचार करें :

सम्मिलन के लिए प्रस्तावित प्रत्येक व्यक्तिगत पंक्ति के लिए, या तो सम्मिलन आगे बढ़ता है, या, यदि एक मध्यस्थ बाधा या सूचकांक द्वारा निर्दिष्ट conflict_targetका उल्लंघन किया जाता है, तो विकल्प conflict_actionलिया जाता है।

बोल्ड जोर मेरा। इसलिए आपको WHEREखंड UPDATE( यू conflict_action) में अद्वितीय सूचकांक में शामिल स्तंभों के लिए विधेय को दोहराने की आवश्यकता नहीं है :

INSERT INTO test_upsert AS tu
       (name   , status, test_field  , identifier, count) 
VALUES ('shaun', 1     , 'test value', 'ident'   , 1)
ON CONFLICT (name, status, test_field) DO UPDATE
SET count = tu.count + 1;
WHERE tu.name = 'shaun' AND tu.status = 1 AND tu.test_field = 'test value'

अद्वितीय उल्लंघन पहले से ही स्थापित करता है कि आपका जोड़ा WHEREक्लॉज क्या बेमानी तरीके से लागू होगा।

आंशिक सूचकांक को स्पष्ट करें

WHEREइसे एक वास्तविक आंशिक अनुक्रमणिका बनाने के लिए एक खंड जोड़ें जैसे कि आपने स्वयं उल्लेख किया है (लेकिन उल्टे तर्क के साथ):

CREATE UNIQUE INDEX test_upsert_partial_idx
ON public.test_upsert (name, status)
WHERE test_field IS NULL;  -- not: "is not null"

अपने UPSERT में इस आंशिक सूचकांक का उपयोग करने के लिए आपको @ypercube जैसे मिलान की आवश्यकता होती है :conflict_target

ON CONFLICT (name, status) WHERE test_field IS NULL

अब उपरोक्त आंशिक सूचकांक अनुमान है। हालाँकि , जैसा कि मैनुअल भी नोट करता है :

[...] एक गैर-आंशिक अनूठे सूचकांक (एक विधेय के बिना एक अद्वितीय सूचकांक) का अनुमान लगाया जाएगा (और इस प्रकार इसका उपयोग किया जाता है ON CONFLICT) यदि ऐसा सूचकांक हर दूसरे मानदंडों को पूरा करता है।

यदि आपके पास केवल (name, status)(यह भी) पर एक अतिरिक्त (या केवल) सूचकांक का उपयोग किया जाता है। एक सूचकांक (name, status, test_field)स्पष्ट रूप से अनुमानित नहीं होगा। यह आपकी समस्या की व्याख्या नहीं करता है, लेकिन हो सकता है कि परीक्षण करते समय भ्रम की स्थिति में शामिल हो गया हो।

समाधान

AIUI, उपरोक्त में से कोई भी आपकी समस्या को हल नहीं करता है , फिर भी। आंशिक सूचकांक के साथ, केवल NULL मान मिलान वाले विशेष मामले पकड़े जाएंगे। और अन्य डुप्लिकेट पंक्तियाँ या तो डाली जाएँगी यदि आपके पास कोई अन्य विशिष्ट अनुक्रमणिका / बाधाएँ मेल नहीं खाती हैं, या यदि आप करते हैं तो एक अपवाद बढ़ाएँ। मुझे लगता है कि वह नहीं है जो आप चाहते हैं। तुम लिखो:

मिश्रित कुंजी 20 स्तंभों से बना है, जिनमें से 10 अशक्त हो सकते हैं।

आप वास्तव में डुप्लिकेट को क्या मानते हैं? Postgres (SQL मानक के अनुसार) दो NULL मानों को समान नहीं मानता है। नियम पुस्तिका:

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

सम्बंधित:

मुझे लगता है कि आपNULLसभी 10 अशक्त स्तंभों में मूल्यों को समान समझनाचाहतेहैं। यह एक अतिरिक्त आंशिक सूचकांक के साथ एक एकल अशक्त स्तंभ को कवर करने के लिए सुरुचिपूर्ण और व्यावहारिक है जैसे यहां प्रदर्शित किया गया है:

लेकिन यह अधिक अशक्त स्तंभों के लिए जल्दी से हाथ से निकल जाता है। आपको अशक्त स्तंभों के हर अलग संयोजन के लिए एक आंशिक सूचकांक की आवश्यकता होगी। उनमें से सिर्फ 2 के लिए जो कि 3 आंशिक सूचकांक हैं (a), (b)और (a,b)। संख्या तेजी के साथ बढ़ रही है 2^n - 1। आपके 10 अशक्त स्तंभों के लिए, NULL मानों के सभी संभावित संयोजनों को कवर करने के लिए, आपको पहले से ही 1023 आंशिक अनुक्रमित की आवश्यकता होगी। नही जाओ।

सरल समाधान: NULL मानों को बदलें और इसमें शामिल स्तंभों को परिभाषित करें NOT NULL, और सब कुछ एक साधारण UNIQUEबाधा के साथ ठीक काम करेगा ।

यदि यह विकल्प नहीं है, तो मैं सूचकांक COALESCEमें NULL को बदलने के लिए एक अभिव्यक्ति सूचकांक का सुझाव देता हूं :

CREATE UNIQUE INDEX test_upsert_solution_idx
    ON test_upsert (name, status, COALESCE(test_field, ''));

खाली स्ट्रिंग ( '') वर्ण प्रकारों के लिए एक स्पष्ट उम्मीदवार है, लेकिन आप किसी भी कानूनी मूल्य का उपयोग कर सकते हैं जो या तो कभी नहीं दिखाई देता है या "अद्वितीय" की आपकी परिभाषा के अनुसार NULL के साथ जोड़ दिया जा सकता है ।

फिर इस कथन का उपयोग करें:

INSERT INTO test_upsert as tu(name,status,test_field,identifier, count) 
VALUES ('shaun', 1, null        , 'ident', 11)  -- works with
     , ('bob'  , 2, 'test value', 'ident', 22)  -- and without NULL
ON     CONFLICT (name, status, COALESCE(test_field, '')) DO UPDATE  -- match expr. index
SET    count = COALESCE(tu.count + EXCLUDED.count, EXCLUDED.count, tu.count);

@Ypercube की तरह मुझे लगता है कि आप वास्तव countमें मौजूदा गिनती में जोड़ना चाहते हैं । चूंकि कॉलम NULL हो सकता है, NULL जोड़ने से कॉलम NULL सेट हो जाएगा। यदि आप परिभाषित करते हैं count NOT NULL, तो आप सरल कर सकते हैं।


एक अन्य विचार के लिए सभी अद्वितीय उल्लंघनों को कवर करने के लिए बयान से बस विरोधाभासी_टैग को छोड़ना होगा । फिर आप "अनूठे" होने की अधिक परिष्कृत परिभाषा के लिए विभिन्न अद्वितीय अनुक्रमितों को परिभाषित कर सकते हैं। लेकिन इसके साथ उड़ान नहीं होगी । एक बार फिर मैनुअल:ON CONFLICT DO UPDATE

के लिए ON CONFLICT DO NOTHING, यह एक विरोधाभास निर्दिष्ट करने के लिए वैकल्पिक है। जब छोड़ा जाता है, तो सभी प्रयोग करने योग्य बाधाओं (और अद्वितीय अनुक्रमित) के साथ संघर्ष को नियंत्रित किया जाता है। के लिए ON CONFLICT DO UPDATE, एक विरोध_टार्ग प्रदान किया जाना चाहिए।


1
अच्छा लगा। मैंने पहली बार प्रश्न को पढ़ते हुए 20-10 कॉलम का हिस्सा छोड़ दिया और बाद में पूरा करने का समय नहीं था। count = CASE WHEN EXCLUDED.count IS NULL THEN tu.count ELSE COALESCE(tu.count, 0) + COALESCE(EXCLUDED.count, 0) ENDकरने के लिए सरल किया जा सकताcount = COALESCE(tu.count+EXCLUDED.count, EXCLUDED.count, tu.count)
ypercubeᵀᴹ

फिर से देखते हुए, मेरा "सरलीकृत" संस्करण इतना स्व-दस्तावेजीकरण नहीं है।
ypercube y 8

@ ypercube y: मैंने आपका सुझाया अद्यतन लागू किया। यह सरल है, धन्यवाद।
एरविन ब्रान्डसेट्टर

@ErwinBrandstetter आप सबसे अच्छे हैं
सीमस एब्सरे

7

मुझे लगता है कि समस्या यह है कि आपके पास आंशिक सूचकांक नहीं है और ON CONFLICTवाक्यविन्यास test_upsert_upsert_id_idxसूचकांक से मेल नहीं खाता है लेकिन अन्य अद्वितीय बाधा है।

यदि आप सूचकांक को आंशिक (साथ WHERE test_field IS NULL) के रूप में परिभाषित करते हैं :

CREATE UNIQUE INDEX test_upsert_upsert_id_idx
ON public.test_upsert
USING btree
(name COLLATE pg_catalog."default", status)
WHERE test_field IS NULL ;

और ये पंक्तियाँ पहले से ही तालिका में हैं:

INSERT INTO test_upsert as tu
    (name, status, test_field, identifier, count) 
VALUES 
    ('shaun', 1, null, 'ident', 1),
    ('maria', 1, null, 'ident', 1) ;

तब क्वेरी सफल होगी:

INSERT INTO test_upsert as tu
    (name, status, test_field, identifier, count) 
VALUES 
    ('peter', 1,   17, 'ident', 1),
    ('shaun', 1, null, 'ident', 3),
    ('maria', 1, null, 'ident', 7)
ON CONFLICT 
    (name, status) WHERE test_field IS NULL   -- the conflicting condition
DO UPDATE SET
    count = tu.count + EXCLUDED.count 
WHERE                                         -- when to update
    tu.name = 'shaun' AND tu.status = 1 ;     -- if you don't want all of the
                                              -- updates to happen

निम्नलिखित परिणामों के साथ:

('peter', 1,   17, 'ident', 1)  -- no conflict: row inserted

('shaun', 1, null, 'ident', 3)  -- conflict: no insert
                           -- matches where: row updated with count = 1+3 = 4

('maria', 1, null, 'ident', 1)  -- conflict: no insert
                     -- doesn't match where: no update

यह स्पष्ट करता है कि आंशिक सूचकांक का उपयोग कैसे किया जाए। लेकिन (मुझे लगता है) यह समस्या को हल नहीं करता है, फिर भी।
इरविन ब्रान्डस्टेट्टर

जब तक कोई अद्यतन नहीं होता है तब तक 'मारिया' की गिनती 1 पर नहीं रहती है?
मप्रदेव

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