MongoDB atomic“ findOrCreate”: findOne แทรกหากไม่มีอยู่ แต่ไม่อัปเดต


114

ตามที่ชื่อบอกฉันต้องการดำเนินการค้นหา (หนึ่ง) สำหรับเอกสารโดย _id และหากไม่มีอยู่ให้สร้างขึ้นไม่ว่าจะถูกค้นพบหรือถูกสร้างขึ้นให้ส่งคืนในการเรียกกลับ

ฉันไม่ต้องการอัปเดตหากมีอยู่อย่างที่ฉันอ่าน findAndModify ฉันเห็นคำถามอื่น ๆ มากมายใน Stackoverflow เกี่ยวกับเรื่องนี้ แต่อีกครั้งไม่ต้องการอัปเดตอะไร

ฉันไม่แน่ใจว่าด้วยการสร้าง (ไม่ได้มีอยู่) นั่นคือการอัปเดตที่ทุกคนพูดถึงจริง ๆ มันทำให้สับสน :(

คำตอบ:


192

เริ่มต้นด้วย MongoDB 2.4 ไม่จำเป็นต้องพึ่งพาดัชนีที่ไม่ซ้ำกันอีกต่อไป (หรือวิธีแก้ปัญหาอื่น ๆ ) สำหรับfindOrCreateการดำเนินการแบบปรมาณู

ขอขอบคุณผู้ประกอบการใหม่ 2.4 ซึ่งจะช่วยให้คุณสามารถระบุการปรับปรุงเท่านั้นที่ควรจะเกิดขึ้นเมื่อใส่เอกสาร$setOnInsert

เมื่อรวมกับupsertตัวเลือกนี้หมายความว่าคุณสามารถใช้findAndModifyเพื่อให้บรรลุการfindOrCreateดำเนินการที่เหมือนอะตอมได้

db.collection.findAndModify({
  query: { _id: "some potentially existing id" },
  update: {
    $setOnInsert: { foo: "bar" }
  },
  new: true,   // return new doc if one is upserted
  upsert: true // insert the document if it does not exist
})

เนื่องจาก$setOnInsertมีผลเฉพาะกับเอกสารที่กำลังแทรกหากพบเอกสารที่มีอยู่จะไม่มีการแก้ไขใด ๆ หากไม่มีเอกสารอยู่เอกสารจะเพิ่มขึ้นหนึ่งรายการด้วย _id ที่ระบุจากนั้นดำเนินการชุดแทรกเท่านั้น ในทั้งสองกรณีเอกสารจะถูกส่งคืน


3
@Gank หากคุณใช้ไดรเวอร์เนทีฟ mongodb สำหรับโหนดไวยากรณ์จะเป็นเช่นcollection.findAndModify({_id:'theId'}, <your sort opts>, {$setOnInsert:{foo: 'bar'}}, {new:true, upsert:true}, callback)นั้น ดูเอกสาร
หมายเลข 1311407

7
มีวิธีที่จะทราบว่าเอกสารได้รับการปรับปรุงหรือแทรกหรือไม่?
วาระ

3
หากคุณต้องการที่จะตรวจสอบว่าแบบสอบถามดังกล่าว (คนdb.collection.findAndModify({query: {_id: "some potentially existing id"}, update: {$setOnInsert: {foo: "bar"}}, new: true, upsert: true})) แทรก (upsert) eddb.collection.updateOne({_id: "some potentially existing id"}, {$setOnInsert: {foo: "bar"}}, {upsert: true})เอกสารคุณควรพิจารณาการใช้ จะคืนค่า{"acknowledged": true, "matchedCount": 0, "modifiedCount": 0, "upsertedId": ObjectId("for newly inserted one")}หากใส่เอกสาร{"acknowledged": true, "matchedCount": 1, "modifiedCount": 0}หากมีเอกสารอยู่แล้ว
КонстантинВан

8
ดูเหมือนว่าจะเลิกใช้ในรสชาติของfindOneAndUpdate, findOneAndReplaceหรือfindOneAndDelete
Ravshan Samandarov

5
สิ่งหนึ่งที่ต้องระวังที่นี่แม้ว่า สิ่งนี้ใช้ได้เฉพาะเมื่อตัวเลือกของ findAndModify / findOneAndUpdate / updateOne ระบุเอกสารหนึ่งโดยไม่ซ้ำกันโดย _id มิฉะนั้นการอัปเดตจะถูกแยกออกจากเซิร์ฟเวอร์เป็นแบบสอบถามและอัปเดต / แทรก การอัปเดตจะยังคงเป็นแบบปรมาณู แต่การสืบค้นและการอัปเดตร่วมกันจะไม่ถูกดำเนินการแบบอะตอม
อานนท์

15

เวอร์ชันไดรเวอร์> 2

เมื่อใช้ไดรเวอร์ล่าสุด (> เวอร์ชัน 2)คุณจะใช้findOneAndUpdateตามที่findAndModifyเลิกใช้แล้ว เมธอดใหม่รับอาร์กิวเมนต์ 3 ตัวfilterคือupdateอ็อบเจกต์ (ซึ่งมีคุณสมบัติดีฟอลต์ของคุณที่ควรถูกแทรกสำหรับอ็อบเจ็กต์ใหม่) และoptionsตำแหน่งที่คุณต้องระบุการดำเนินการที่เพิ่มขึ้น

การใช้ไวยากรณ์สัญญาจะมีลักษณะดังนี้:

const result = await collection.findOneAndUpdate(
  { _id: new ObjectId(id) },
  {
    $setOnInsert: { foo: "bar" },
  },
  {
    returnOriginal: false,
    upsert: true,
  }
);

const newOrUpdatedDocument = result.value;

ขอบคุณฉันreturnOriginalควรจะreturnNewDocumentคิด - docs.mongodb.com/manual/reference/method/…
Dominic

ไม่เป็นไรไดรเวอร์โหนดใช้งานแตกต่างกันและคำตอบของคุณถูกต้อง
Dominic

6

มันค่อนข้างสกปรก แต่คุณสามารถใส่เข้าไปได้

ตรวจสอบให้แน่ใจว่าคีย์มีดัชนีที่ไม่ซ้ำกัน (หากคุณใช้ _id ก็ใช้ได้แสดงว่าไม่ซ้ำกันแล้ว)

ด้วยวิธีนี้หากมีองค์ประกอบอยู่แล้วจะส่งคืนข้อยกเว้นที่คุณสามารถจับได้

หากไม่มีอยู่เอกสารใหม่จะถูกแทรก

อัปเดต:คำอธิบายโดยละเอียดเกี่ยวกับเทคนิคนี้ในเอกสาร MongoDB


โอเคนั่นเป็นความคิดที่ดี แต่สำหรับค่าที่มีอยู่แล้วมันจะส่งคืนข้อผิดพลาด แต่ไม่ใช่ค่าตัวเองใช่ไหม
Discipol

3
นี่เป็นหนึ่งในโซลูชันที่แนะนำสำหรับลำดับการดำเนินการที่แยกได้ (ค้นหาจากนั้นสร้างหากไม่พบสันนิษฐานว่า) docs.mongodb.org/manual/tutorial/isolate-sequence-of-operations
หมายเลข 1311407

@Discipol หากคุณต้องการทำชุดปฏิบัติการปรมาณูก่อนอื่นคุณควรล็อกเอกสารจากนั้นแก้ไขและในตอนท้ายให้ปล่อย สิ่งนี้จะต้องมีการสืบค้นเพิ่มเติม แต่คุณสามารถปรับให้เหมาะสมกับสถานการณ์กรณีและปัญหาที่ดีที่สุดและทำเพียง 1-2 แบบสอบถามเกือบตลอดเวลา ดู: docs.mongodb.org/manual/tutorial/perform-two-phase-commits
Madarco

1

นี่คือสิ่งที่ฉันทำ (ไดรเวอร์ Ruby MongoDB):

$db[:tags].update_one({:tag => 'flat'}, {'$set' => {:tag => 'earth' }}, { :upsert => true })}

มันจะอัปเดตหากมีอยู่และแทรกหากไม่มี

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.