ชี้แจง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 สองค่าให้เท่ากัน คู่มือ:
โดยทั่วไปข้อ จำกัด ที่ไม่ซ้ำกันจะถูกละเมิดหากมีมากกว่าหนึ่งแถวในตารางที่ค่าของคอลัมน์ทั้งหมดที่รวมอยู่ในข้อ จำกัด มีค่าเท่ากัน อย่างไรก็ตามค่า null สองค่าจะไม่ถือว่าเท่ากันในการเปรียบเทียบนี้ นั่นหมายความว่าแม้ในกรณีที่มีข้อ จำกัด ที่ไม่ซ้ำกันคุณสามารถจัดเก็บแถวที่ซ้ำกันที่มีค่า Null ในคอลัมน์ที่ จำกัด อย่างน้อยหนึ่งคอลัมน์ พฤติกรรมนี้เป็นไปตามมาตรฐาน SQL แต่เราเคยได้ยินว่าฐานข้อมูล SQL อื่นอาจไม่ปฏิบัติตามกฎนี้ ดังนั้นควรระมัดระวังเมื่อพัฒนาแอพพลิเคชั่นที่ต้องการพกพา
ที่เกี่ยวข้อง:
ฉันสมมติว่าคุณต้องการให้NULL
ค่าในคอลัมน์ที่สามารถ nullable ทั้ง 10 คอลัมน์ถือว่าเท่ากันได้ มันสวยงามและใช้งานได้จริงเพื่อครอบคลุมคอลัมน์ nullable เดียวด้วยดัชนีบางส่วนเพิ่มเติมเช่นที่แสดงที่นี่:
แต่สิ่งนี้อยู่นอกมืออย่างรวดเร็วสำหรับคอลัมน์ที่ไม่มีค่าได้ คุณต้องการดัชนีบางส่วนสำหรับชุดค่าผสมที่แตกต่างกันทุกคอลัมน์ เพียง 2 ของผู้ที่เป็น 3 ดัชนีบางส่วนสำหรับ(a)
, และ(b)
จำนวนที่มีการเติบโตชี้แจงกับ(a,b)
2^n - 1
สำหรับคอลัมน์ที่ไม่มีค่าได้ 10 คอลัมน์เพื่อให้ครอบคลุมค่า NULL ที่เป็นไปได้ทั้งหมดคุณต้องมีดัชนีบางส่วนแล้ว 1,023 รายการ ไม่ไป.
วิธีแก้ปัญหาง่าย ๆ : แทนที่ค่า NULL และกำหนดคอลัมน์ที่เกี่ยวข้องNOT NULL
และทุกอย่างจะใช้ได้ดีกับUNIQUE
ข้อ จำกัดง่ายๆ
หากไม่ใช่ตัวเลือกฉันขอแนะนำให้ดัชนีนิพจน์ด้วยCOALESCE
เพื่อแทนที่ NULL ในดัชนี:
CREATE UNIQUE INDEX test_upsert_solution_idx
ON test_upsert (name, status, COALESCE(test_field, ''));
สตริงที่ว่างเปล่า ( ''
) เป็นผู้สมัครที่ชัดเจนสำหรับรูปแบบตัวอักษร, แต่คุณสามารถใช้ใด ๆทางกฎหมายที่อาจไม่เคยปรากฏหรือสามารถพับเก็บด้วยโมฆะตามที่คุณนิยามของ "ที่ไม่ซ้ำกัน"
จากนั้นใช้คำสั่งนี้:
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
คุณสามารถทำให้ง่ายขึ้น
ความคิดก็จะเป็นเพียงวางconflict_targetจากงบเพื่อให้ครอบคลุมการละเมิดที่ไม่ซ้ำกันทั้งหมด จากนั้นคุณสามารถกำหนดดัชนีเฉพาะต่าง ๆ สำหรับคำจำกัดความที่ซับซ้อนยิ่งขึ้นของสิ่งที่ควรจะเป็น "ไม่ซ้ำกัน" ON CONFLICT DO UPDATE
แต่ที่จะไม่บินกับ คู่มืออีกครั้ง:
สำหรับON CONFLICT DO NOTHING
, มันเป็นทางเลือกที่จะระบุความขัดแย้ง _target; เมื่อละเว้นจะขัดแย้งกับข้อ จำกัด ที่ใช้งานได้ทั้งหมด (และดัชนีที่ไม่ซ้ำกัน) ได้รับการจัดการ สำหรับต้องON CONFLICT DO UPDATE
มีการขัดแย้งขัดแย้งกับเป้าหมาย
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)