วิธีอัปเดต _id ของเอกสาร MongoDB หนึ่งชุด


129

ฉันต้องการอัปเดต_idฟิลด์ของเอกสารเดียว ฉันรู้ว่ามันไม่ใช่แนวปฏิบัติที่ดีจริงๆ แต่ด้วยเหตุผลทางเทคนิคฉันต้องอัปเดต หากฉันพยายามอัปเดตฉันจะได้รับ:

> db.clients.update({ _id: ObjectId("123")}, { $set: { _id: ObjectId("456")}})

Performing an update on the path '_id' would modify the immutable field '_id'

และไม่ได้ทำการอัพเดต. ฉันจะอัปเดตได้อย่างไร

คำตอบ:


216

คุณไม่สามารถอัปเดตได้ คุณจะต้องบันทึกเอกสารโดยใช้เอกสารใหม่_idจากนั้นจึงนำเอกสารเก่าออก

// store the document in a variable
doc = db.clients.findOne({_id: ObjectId("4cc45467c55f4d2d2a000002")})

// set a new _id on the document
doc._id = ObjectId("4c8a331bda76c559ef000004")

// insert the document, using the new _id
db.clients.insert(doc)

// remove the document with the old _id
db.clients.remove({_id: ObjectId("4cc45467c55f4d2d2a000002")})

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

จุดดี @skelly! ฉันบังเอิญคิดถึงปัญหาที่คล้ายกันและเห็นความคิดเห็นใหม่ของคุณเมื่อ 2 ชั่วโมงที่แล้ว ดังนั้นความยุ่งยากในการแก้ไข id นี้ถือเป็นปัญหาภายในที่เกิดจากการอนุญาตให้ผู้ใช้เลือก ID?
RayLuo

1
ถ้าคุณได้รับduplicate key errorในinsertบรรทัดและไม่ได้กังวลเกี่ยวกับปัญหาดังกล่าว @skelly ทางออกที่ง่ายที่สุดคือการเพียงโทรremoveบรรทัดแรกและจากนั้นเรียกinsertสาย docควรพิมพ์ที่อยู่บนหน้าจอของคุณดังนั้นมันจะเป็นเรื่องง่ายที่จะกู้คืนกรณีที่เลวร้ายแม้ว่าแทรกล้มเหลวสำหรับเอกสารที่เรียบง่าย
philfreo

การใช้เพียง ObjectId () โดยไม่มีสตริงเป็นพารามิเตอร์จะสร้างใหม่ที่ไม่ซ้ำกัน
Erik

1
@ShankhadeepGhoshal ใช่นั่นเป็นความเสี่ยงโดยเฉพาะอย่างยิ่งถ้าคุณกำลังดำเนินการกับระบบการผลิตสด น่าเสียดายที่ฉันคิดว่าตัวเลือกที่ดีที่สุดของคุณคือการหยุดทำงานตามกำหนดเวลาและหยุดนักเขียนทั้งหมดในระหว่างกระบวนการนี้ อีกทางเลือกหนึ่งที่อาจเจ็บปวดน้อยกว่าเล็กน้อยคือการบังคับให้แอปพลิเคชันเข้าสู่โหมดอ่านอย่างเดียวชั่วคราว กำหนดค่าแอปทั้งหมดใหม่ที่เขียน tot he DB เพื่อชี้ไปที่โหนดรองเท่านั้น การอ่านจะประสบความสำเร็จ แต่การเขียนจะล้มเหลวในช่วงเวลานี้และฐานข้อมูลของคุณจะคงที่
skelly

32

หากต้องการทำสำหรับคอลเลกชันทั้งหมดของคุณคุณยังสามารถใช้ลูป (ตามตัวอย่างของ Niels):

db.status.find().forEach(function(doc){ 
    doc._id=doc.UserId; db.status_new.insert(doc);
});
db.status_new.renameCollection("status", true);

ในกรณีนี้ UserId คือ ID ใหม่ที่ฉันต้องการใช้


1
จะแนะนำให้ใช้ snapshot () ในการค้นหาเพื่อป้องกันไม่ให้ forEach หยิบเอกสารใหม่ขึ้นมาโดยไม่ได้ตั้งใจขณะที่ทำซ้ำ
John Flinchbaugh

ข้อมูลโค้ดนี้จะไม่เสร็จสมบูรณ์ มันยังคงวนซ้ำคอลเลกชั่นตลอดไป สแนปชอตไม่เป็นไปตามที่คุณคาดหวัง (คุณสามารถทดสอบได้โดยใช้ 'สแนปชอต' เพิ่มเอกสารลงในคอลเลกชันจากนั้นจะเห็นว่าเอกสารใหม่นั้นอยู่ในสแนปชอต)
แพทริค

ดูstackoverflow.com/a/28083980/305324สำหรับทางเลือกอื่นสำหรับสแนปชอต list()เป็นตรรกะ แต่สำหรับฐานข้อมูลขนาดใหญ่สิ่งนี้อาจทำให้หน่วยความจำหมดลง
Patrick

4
เอ่อนี่มี 11 upvotes แต่มีคนบอกว่ามันวนซ้ำไม่สิ้นสุด? ข้อตกลงที่นี่คืออะไร?
แอนดรู

4
@ แอนดรูว์เพราะวัฒนธรรมป๊อปโคเดอร์ร่วมสมัยบอกว่าคุณควรรับทราบข้อมูลที่ดีก่อนที่จะตรวจสอบว่าอินพุตนั้นใช้งานได้จริง
csvan

5

ในกรณีที่คุณต้องการเปลี่ยนชื่อ _id ในคอลเล็กชันเดียวกัน (ตัวอย่างเช่นหากคุณต้องการนำหน้า _ids):

db.someCollection.find().snapshot().forEach(function(doc) { 
   if (doc._id.indexOf("2019:") != 0) {
       print("Processing: " + doc._id);
       var oldDocId = doc._id;
       doc._id = "2019:" + doc._id; 
       db.someCollection.insert(doc);
       db.someCollection.remove({_id: oldDocId});
   }
});

if (doc._id.indexOf ("2019:")! = 0) {...จำเป็นเพื่อป้องกันการวนซ้ำแบบไม่สิ้นสุดเนื่องจาก forEach เลือกเอกสารที่แทรกแม้จะใช้วิธีthrought .snapshot ()


find(...).snapshot is not a functionแต่นอกเหนือจากนั้นวิธีแก้ปัญหาที่ยอดเยี่ยม นอกจากนี้หากคุณต้องการแทนที่_idด้วยรหัสที่กำหนดเองของคุณคุณสามารถตรวจสอบได้ว่าdoc._id.toString().length === 24จะป้องกันการวนซ้ำแบบไม่สิ้นสุดหรือไม่ (สมมติว่า ID ที่กำหนดเองของคุณมีความยาวไม่เกิน24 ตัวอักษร )
Dan Dascalescu

1

ที่นี่ฉันมีวิธีแก้ปัญหาที่หลีกเลี่ยงการร้องขอหลายรายการสำหรับการวนซ้ำและการลบเอกสารเก่า

คุณสามารถสร้างแนวคิดใหม่ด้วยตนเองได้อย่างง่ายดายโดยใช้สิ่งต่างๆเช่น: _id:ObjectId() แต่การรู้ว่า Mongo จะกำหนด _id โดยอัตโนมัติหากขาดหายไปคุณสามารถใช้การรวมเพื่อสร้าง$projectฟิลด์ทั้งหมดในเอกสารของคุณ แต่ละเว้นฟิลด์ _id จากนั้นคุณสามารถบันทึกด้วยไฟล์$out

ดังนั้นหากเอกสารของคุณคือ:

{
"_id":ObjectId("5b5ed345cfbce6787588e480"),
"title": "foo",
"description": "bar"
}

จากนั้นคำถามของคุณจะเป็น:

    db.getCollection('myCollection').aggregate([
        {$match:
             {_id: ObjectId("5b5ed345cfbce6787588e480")}
        }        
        {$project:
            {
             title: '$title',
             description: '$description'             
            }     
        },
        {$out: 'myCollection'}
    ])

ความคิดที่น่าสนใจ ... แต่คุณมักจะต้องการตั้งค่า_idเป็นค่าที่กำหนดแทนที่จะให้ MongoDB สร้างขึ้นมาใหม่
Dan Dascalescu

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