นี่คือการตัดสินใจดำเนินการ มันเป็นเรื่องที่อธิบายไว้ในเอกสาร Postgres, WITH
แบบสอบถาม (Common ตารางนิพจน์) มีสองย่อหน้าที่เกี่ยวข้องกับปัญหานี้
ก่อนเหตุผลของพฤติกรรมที่สังเกต:
ย่อยงบในการWITH
ที่จะดำเนินการควบคู่กันไปกับแต่ละอื่น ๆและมีแบบสอบถามหลัก ดังนั้นเมื่อใช้คำสั่ง data-modifying ในWITH
ลำดับที่การอัพเดทที่ระบุเกิดขึ้นจริงจะไม่สามารถคาดเดาได้ งบทั้งหมดจะถูกดำเนินการด้วยภาพรวมเดียวกัน (ดูบทที่ 13) ดังนั้นพวกเขาไม่สามารถ "เห็น" ผลกระทบของอีกคนหนึ่งในตารางเป้าหมาย สิ่งนี้ช่วยลดผลกระทบของความไม่แน่นอนของลำดับการอัพเดตแถวจริงและหมายความว่าRETURNING
ข้อมูลเป็นวิธีเดียวที่จะสื่อสารการเปลี่ยนแปลงระหว่างWITH
คำสั่งย่อยต่างๆและแบบสอบถามหลัก ตัวอย่างของสิ่งนี้คือใน ...
หลังจากที่ฉันโพสต์ข้อเสนอแนะพร้อมกับpgsql-docsมาร์โค Tiikkaja อธิบาย (ซึ่งเห็นด้วยกับคำตอบของเออร์วิน):
กรณีแทรกการอัพเดตและการลบแทรกไม่ทำงานเนื่องจาก UPDATE และ DELETE ไม่มีวิธีการดูแถวแทรกเนื่องจากสแน็ปช็อตถูกยึดก่อนที่จะเกิด INSERT ไม่มีอะไรที่คาดเดาเกี่ยวกับสองกรณีนี้
ดังนั้นเหตุผลที่คำสั่งของคุณไม่อัปเดตสามารถอธิบายได้ในย่อหน้าแรกข้างต้น (เกี่ยวกับ "ภาพรวม") จะเกิดอะไรขึ้นเมื่อคุณปรับเปลี่ยน CTE คือพวกเขาทั้งหมดและแบบสอบถามหลักจะถูกดำเนินการและ "ดู" ภาพรวมที่เหมือนกันของข้อมูล (ตาราง) เหมือนเดิมทันทีก่อนการดำเนินการคำสั่ง CTE สามารถส่งผ่านข้อมูลเกี่ยวกับสิ่งที่แทรก / อัปเดต / ลบไปยังสิ่งอื่นและไปยังคิวรีหลักโดยใช้ส่วนRETURNING
คำสั่ง แต่พวกเขาไม่สามารถเห็นการเปลี่ยนแปลงในตารางได้โดยตรง ดังนั้นให้ดูว่าเกิดอะไรขึ้นในคำสั่งของคุณ:
WITH newval AS (
INSERT INTO tbl(val) VALUES (1) RETURNING id
) UPDATE tbl SET val=2 FROM newval WHERE tbl.id=newval.id;
เรามี 2 ส่วนคือ CTE ( newval
):
-- newval
INSERT INTO tbl(val) VALUES (1) RETURNING id
และแบบสอบถามหลัก:
-- main
UPDATE tbl SET val=2 FROM newval WHERE tbl.id=newval.id
การไหลของการดำเนินการเป็นดังนี้:
initial data: tbl
id │ val
(empty)
/ \
/ \
/ \
newval: \
tbl (after newval) \
id │ val \
1 │ 1 |
|
newval: returns |
id |
1 |
\ |
\ |
\ |
main query
ดังนั้นเมื่อแบบสอบถามหลักเข้าร่วมtbl
(ตามที่เห็นในภาพรวม) กับnewval
ตารางนั้นจะรวมตารางว่างที่มีตาราง 1 แถว เห็นได้ชัดว่ามันอัปเดต 0 แถว ดังนั้นคำสั่งไม่เคยมาแก้ไขแถวที่แทรกใหม่และนั่นคือสิ่งที่คุณเห็น
ทางออกในกรณีของคุณคือการเขียนคำสั่งใหม่เพื่อแทรกค่าที่ถูกต้องในสถานที่แรกหรือใช้ 2 คำสั่ง หนึ่งที่แทรกและที่สองในการปรับปรุง
มีสถานการณ์อื่น ๆ ที่คล้ายกันเช่นถ้าข้อความมีINSERT
และจากนั้นDELETE
ในแถวเดียวกัน การลบจะล้มเหลวด้วยเหตุผลเดียวกันทั้งหมด
บางกรณีมีการอัปเดตและอัปเดตลบและพฤติกรรมของพวกเขาอธิบายไว้ในย่อหน้าต่อไปนี้ในหน้าเอกสารเดียวกัน
การพยายามอัปเดตแถวเดียวกันสองครั้งในคำสั่งเดียวไม่ได้รับการสนับสนุน มีการแก้ไขอย่างใดอย่างหนึ่งเท่านั้น แต่ไม่ใช่เรื่องง่าย (และบางครั้งก็เป็นไปไม่ได้) ที่จะทำนายได้อย่างน่าเชื่อถือ นอกจากนี้ยังนำไปใช้กับการลบแถวที่ได้รับการปรับปรุงแล้วในคำสั่งเดียวกัน: ดำเนินการอัพเดตเท่านั้น ดังนั้นโดยทั่วไปคุณควรหลีกเลี่ยงการพยายามแก้ไขแถวเดี่ยวสองครั้งในคำสั่งเดียว โดยเฉพาะอย่างยิ่งหลีกเลี่ยงการเขียนด้วยคำสั่งย่อยที่อาจส่งผลกระทบต่อแถวเดียวกันที่เปลี่ยนแปลงโดยคำสั่งหลักหรือคำสั่งย่อยพี่น้อง ผลกระทบของข้อความดังกล่าวจะไม่สามารถคาดการณ์ได้
และในการตอบกลับจาก Marko Tiikkaja:
เคส update-update และ update-delete ไม่ได้เกิดจากรายละเอียดการใช้งานที่เหมือนกันอย่างชัดเจน(เช่น case-update และ insert-delete cases)
เคส update-update ไม่ทำงานเพราะภายในดูเหมือนว่าปัญหาฮัลโลวีนและ Postgres ไม่มีทางรู้ว่าทูเปิลตัวไหนที่จะโอเคที่จะอัปเดตสองครั้งและอันไหนที่สามารถนำปัญหาฮัลโลวีนกลับมาใช้ได้
ดังนั้นเหตุผลจึงเหมือนกัน (วิธีการปรับใช้ CTEs จะดำเนินการอย่างไรและแต่ละ CTE เห็นสแน็ปช็อตเดียวกัน) อย่างไร แต่รายละเอียดแตกต่างกันใน 2 กรณีนี้เนื่องจากซับซ้อนกว่าและผลลัพธ์ไม่สามารถคาดการณ์ได้ในกรณีอัปเดต
ในการแทรกการอัพเดท (เป็นกรณีของคุณ) และการแทรกการลบที่คล้ายกันผลลัพธ์ที่คาดการณ์ได้ เฉพาะการแทรกเกิดขึ้นเนื่องจากการดำเนินการครั้งที่สอง (อัปเดตหรือลบ) ไม่มีวิธีดูและส่งผลต่อแถวที่แทรกใหม่
วิธีแก้ปัญหาที่แนะนำนั้นเหมือนกันสำหรับทุกกรณีที่พยายามแก้ไขแถวเดียวกันมากกว่าหนึ่งครั้ง: อย่าทำ อาจเขียนคำสั่งที่แก้ไขแต่ละแถวหนึ่งครั้งหรือใช้คำสั่ง (2 หรือมากกว่า) แยกกัน