নাল মান সহ PostgreSQL ইউপিএসআরটি ইস্যু


13

পোস্টগ্রিস 9.5-এ নতুন ইউপিএসআরটি বৈশিষ্ট্যটি ব্যবহার করে আমার একটি সমস্যা হচ্ছে

আমার কাছে একটি টেবিল রয়েছে যা অন্য টেবিলের ডেটা একত্রিত করার জন্য ব্যবহৃত হয়। সংমিশ্রিত কীটি 20 টি কলাম দ্বারা গঠিত, যার মধ্যে 10 টি স্থূল হতে পারে। নীচে আমি যে সমস্যাটি করছি তার একটি ছোট সংস্করণ তৈরি করেছি, বিশেষত ন্যূনেল মান সহ।

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

যাইহোক, এটি একই ফলাফল দেয়, হয় একাধিক নাল সারি beingোকানো হচ্ছে বা errorোকানোর চেষ্টা করার সময় এই ত্রুটি বার্তাটি:

ত্রুটি: ওয়ান কনফ্লিক্ট স্পেসিফিকেশনের সাথে মেলে কোনও অনন্য বা বর্ধনের সীমাবদ্ধতা নেই

আমি ইতিমধ্যে আংশিক সূচকগুলিতে অতিরিক্ত বিশদ যুক্ত করার চেষ্টা করেছি WHERE test_field is not null OR identifier is not null। তবে সন্নিবেশ করার সময় আমি সীমাবদ্ধতা ত্রুটি বার্তাটি পাই।

উত্তর:


15

নির্মল ON CONFLICT DO UPDATEআচরণ

ম্যানুয়ালটি এখানে বিবেচনা করুন :

সন্নিবেশের জন্য প্রস্তাবিত প্রতিটি স্বতন্ত্র সারির জন্য, হয় সন্নিবেশ এগিয়ে যায়, বা যদি সালিসি বাধা বা নির্দিষ্ট করে সূচক conflict_targetলঙ্ঘন করা হয়, বিকল্প conflict_actionনেওয়া হয়।

বোল্ড জোর আমার। সুতরাং আপনাকে ( WHEREদ ) এর দফায় স্বতন্ত্র সূচীতে অন্তর্ভুক্ত কলামগুলির জন্য পূর্বাভাসগুলি পুনরাবৃত্তি করতে হবে না :UPDATEconflict_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"

আপনার ইউপিএসআরটিতে এই আংশিক সূচকটি ব্যবহার করার জন্য আপনার @ ম্যাসেজের মতো প্রদর্শিত একটি মিল দরকার :conflict_target

ON CONFLICT (name, status) WHERE test_field IS NULL

এখন উপরের আংশিক সূচকটি অনুমান করা হয়েছে। তবে ম্যানুয়ালটিতে যেমন উল্লেখ করা হয়েছে :

[...] একটি অন-আংশিক অনন্য সূচক (প্রাকটিক্যাল ব্যতীত একটি অনন্য সূচক) অনুমান করা হবে (এবং এভাবে ব্যবহার করা হবে ON CONFLICT) যদি অন্য সূত্রগুলি সন্তুষ্ট করে এমন প্রতিটি সূচক উপলব্ধ থাকে।

আপনার যদি অতিরিক্ত (বা শুধুমাত্র) সূচক থাকে তবে এটি (name, status)(এছাড়াও) ব্যবহৃত হবে। একটি সূচক (name, status, test_field)স্পষ্টভাবে অনুমান করা হবে না । এটি আপনার সমস্যার ব্যাখ্যা দেয় না, তবে পরীক্ষার সময় বিভ্রান্তি বাড়িয়ে তোলে।

সমাধান

এআইআইআই, উপরের কোনওটিই আপনার সমস্যার সমাধান করে না। আংশিক সূচকের সাথে, কেবলমাত্র ন্যূুল মানগুলির সাথে বিশেষ কিছু পাওয়া যায়। এবং অন্য সদৃশ সারিগুলি eitherোকানো হবে যদি আপনার কাছে অন্য কোনও মিলের অনন্য সূচক / সীমাবদ্ধতা না থাকে, বা যদি আপনি এটি করেন তবে একটি ব্যতিক্রম বাড়ান। আমি মনে করি এটি আপনি চান না তুমি লেখ:

সংমিশ্রিত কীটি 20 টি কলাম দ্বারা গঠিত, যার মধ্যে 10 টি স্থূল হতে পারে।

আপনি একটি সদৃশ ঠিক কি বিবেচনা? পোস্টগ্রিস (এসকিউএল স্ট্যান্ডার্ড অনুযায়ী) দুটি নূন্যমূল্যকে সমান হিসাবে বিবেচনা করে না। ম্যানুয়াল:

সাধারণভাবে, সারণীতে একাধিক সারি থাকলে সীমাবদ্ধতায় অন্তর্ভুক্ত সমস্ত কলামের মান সমান হলে একটি অনন্য বাধা লঙ্ঘন করা হয়। যাইহোক, দুটি নাল মান এই তুলনায় কখনও সমান বিবেচিত হয় না। এর অর্থ এমনকি অনন্য প্রতিবন্ধকতার উপস্থিতিতেও অনুলিপিযুক্ত সারিগুলি সংরক্ষণ করা সম্ভব যা অন্তত একটি সীমাবদ্ধ কলামগুলিতে নাল মান রাখে। এই আচরণটি এসকিউএল স্ট্যান্ডার্ডের সাথে সঙ্গতিপূর্ণ তবে আমরা শুনেছি যে অন্যান্য এসকিউএল ডাটাবেসগুলি এই নিয়মটি অনুসরণ না করে। তাই পোর্টেবল হওয়ার উদ্দেশ্যে তৈরি অ্যাপ্লিকেশনগুলি বিকাশ করার সময় সাবধান হন।

সম্পর্কিত:

আমি ধরে নিলামNULL আপনি সমস্ত 10 টি কলামের মানগুলি সমান হিসাবে বিবেচনাকরতে চান। এখানে প্রদর্শিত যেমন একটি অতিরিক্ত আংশিক সূচী সহ একটি একক nallable কলাম কভার করা মার্জিত এবং ব্যবহারিক:

তবে আরও নালামযোগ্য কলামগুলির জন্য এটি দ্রুত হাতছাড়া হয়ে যায়। নালামযোগ্য কলামগুলির প্রতিটি স্বতন্ত্র সমন্বয়ের জন্য আপনার আংশিক সূচক প্রয়োজন index মাত্র 2 তাদের জন্য 3 আংশিক ইনডেক্স যে এর জন্য (a), (b)এবং (a,b)। সংখ্যাটি তত দ্রুত বাড়ছে 2^n - 1। আপনার 10 টি নালামযোগ্য কলামগুলির জন্য, NULL মানগুলির সমস্ত সম্ভাব্য সংমিশ্রণগুলি কভার করতে আপনার ইতিমধ্যে 1023 আংশিক সূচক প্রয়োজন। যাও না।

সহজ সমাধান: নুল মানগুলি প্রতিস্থাপন করুন এবং জড়িত কলামগুলি সংজ্ঞায়িত করুন NOT NULLএবং সমস্ত কিছু একটি UNIQUEসীমাবদ্ধতার সাথে ঠিক কাজ করবে ।

যদি এটি কোনও বিকল্প না হয় তবে আমি সূচকে COALESCENULL প্রতিস্থাপনের জন্য একটি এক্সপ্রেশন সূচকটি প্রস্তাব করি :

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

@ টাইপারউবের মত আমিও ধরে নিলাম আপনি আসলে countবিদ্যমান গণনায় যোগ করতে চান । যেহেতু কলামটি নুল হতে পারে, তাই এনওএল যুক্ত করে কলামটি ন্যূনাল সেট করা হবে। আপনি যদি সংজ্ঞা দেন তবে আপনি count NOT NULLসরল করতে পারবেন।


অন্য ধারণাটি হ'ল সমস্ত অনন্য লঙ্ঘন coverেকে দেওয়ার জন্য বিবৃতি থেকে দ্বন্দ্ব_দলটি বাদ দিন । তারপরে আপনি "অনন্য" হওয়ার কথা বলে এর আরও পরিশীলিত সংজ্ঞার জন্য আপনি বিভিন্ন অনন্য সূচী সংজ্ঞায়িত করতে পারেন। কিন্তু যে সঙ্গে উড়ে না । ম্যানুয়ালটি আরও একবার:ON CONFLICT DO UPDATE

কারণ ON CONFLICT DO NOTHING, এটি একটি সংঘাত_মার্কেট নির্দিষ্ট করার জন্য isচ্ছিক; বাদ দেওয়া হলে, সমস্ত ব্যবহারযোগ্য বাধার (এবং অনন্য সূচক) এর সাথে দ্বন্দ্বগুলি পরিচালনা করা হয়। জন্য 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ᵀᴹ

@ ইয়পারক्यूबᵀᴹ: আমি আপনার প্রস্তাবিত আপডেটটি প্রয়োগ করেছি। এটা সহজ, ধন্যবাদ।
এরউইন ব্র্যান্ডস্টেটার

@ এরউইন ব্র্যান্ডসটেটার আপনি সেরা
সমুদ্র আবশের

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ᵀᴹ
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.