Bagaimana cara memperbarui _id dari satu Dokumen MongoDB?


129

Saya ingin memperbarui _idbidang satu dokumen. Saya tahu ini bukan praktik yang sangat baik. Tetapi dengan beberapa alasan teknis, saya perlu memperbaruinya. Jika saya mencoba memperbaruinya saya mendapatkan:

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

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

Dan pembaruan tidak dilakukan. Bagaimana saya bisa memperbaruinya?

Jawaban:


216

Anda tidak dapat memperbaruinya. Anda harus menyimpan dokumen menggunakan yang baru _id, dan kemudian menghapus dokumen yang lama.

// 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
Masalah menyenangkan dengan ini muncul jika beberapa bidang pada dokumen itu memiliki indeks unik. Dalam situasi itu, contoh Anda akan gagal karena dokumen tidak dapat dimasukkan dengan nilai duplikat di bidang indeks yang unik. Anda dapat memperbaikinya dengan melakukan penghapusan terlebih dahulu, tetapi itu adalah ide yang buruk karena jika insert Anda gagal karena suatu alasan data Anda sekarang hilang. Sebagai gantinya, Anda harus menjatuhkan indeks, melakukan pekerjaan, lalu mengembalikan indeks.
Skelly

Poin bagus @kelly! Saya kebetulan memikirkan masalah yang sama dan melihat komentar segar Anda dibuat hanya 2 jam yang lalu. Jadi kerumitan id modifikasi ini dianggap sebagai masalah intrinsik yang disebabkan oleh memungkinkan pengguna untuk memilih ID?
RayLuo

1
Jika Anda mendapat duplicate key errorjawaban insertdan tidak khawatir dengan masalah @skelly yang disebutkan, solusi paling sederhana adalah dengan menelepon removeterlebih dahulu dan kemudian menelepon insert. The docharus sudah dicetak pada layar Anda sehingga akan mudah untuk pulih, kasus terburuk, bahkan jika insert gagal, untuk dokumen sederhana.
philfreo

Menggunakan just ObjectId () tanpa string sebagai parameter akan menghasilkan yang unik baru.
Erik

1
@ShankhadeepGhoshal ya itu risiko, terutama jika Anda melakukan ini terhadap sistem produksi langsung. Sayangnya saya pikir pilihan terbaik Anda di sana adalah mengambil pemadaman terjadwal dan menghentikan semua penulis selama proses ini. Opsi lain yang mungkin sedikit kurang menyakitkan adalah memaksa aplikasi ke mode baca-saja sementara. Konfigurasikan ulang semua aplikasi yang menulis tot DB hanya untuk menunjuk ke simpul sekunder. Membaca akan berhasil tetapi penulisan akan gagal selama waktu ini, dan DB Anda akan tetap statis.
Skelly

32

Untuk melakukannya untuk seluruh koleksi Anda, Anda juga dapat menggunakan loop (berdasarkan contoh Niels):

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

Dalam hal ini, UserId adalah ID baru yang ingin saya gunakan


1
Apakah snapshot () pada find disarankan untuk menjaga forEach dari secara tidak sengaja mengambil dokumen yang lebih baru karena iterates?
John Flinchbaugh

Cuplikan kode ini tidak pernah selesai. Itu terus berulang berulang koleksi. Snapshot tidak melakukan apa yang Anda harapkan (Anda dapat mengujinya dengan mengambil 'snapshot' menambahkan dokumen ke koleksi, lalu melihat bahwa dokumen baru itu ada dalam snapshot)
Patrick

Lihat stackoverflow.com/a/28083980/305324 untuk alternatif snapshot. list()adalah yang logis, tetapi untuk database besar ini dapat menguras memori
Patrick

4
Uh, ini memiliki 11 upvotes tetapi seseorang mengatakan ini adalah infinite loop? Apa masalahnya di sini?
Andrew

4
@Andrew karena budaya pop-coder kontemporer menentukan bahwa Anda harus selalu mengakui input yang baik sebelum benar-benar memverifikasi bahwa input tersebut benar-benar berfungsi.
csvan

5

Jika Anda ingin mengganti nama _id dalam koleksi yang sama (misalnya, jika Anda ingin awalan beberapa _id):

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) {... diperlukan untuk mencegah infinite loop, karena forEach memilih docs yang dimasukkan, bahkan metode .snapshot () yang digunakan.


find(...).snapshot is not a functiontapi selain itu, solusi bagus. Juga, jika Anda ingin mengganti _iddengan id khusus Anda, Anda dapat memeriksa apakah doc._id.toString().length === 24untuk mencegah loop tak terbatas (dengan asumsi ID kustom Anda juga tidak sepanjang 24 karakter ),
Dan Dascalescu

1

Di sini saya memiliki solusi yang menghindari beberapa permintaan, untuk loop dan penghapusan dokumen lama.

Anda dapat dengan mudah membuat ide baru secara manual menggunakan sesuatu seperti: _id:ObjectId() Tapi mengetahui Mongo akan secara otomatis menetapkan _id jika hilang, Anda dapat menggunakan agregat untuk membuat yang $projectberisi semua bidang dokumen Anda, tetapi hilangkan bidang _id. Anda kemudian dapat menyimpannya dengan$out

Jadi jika dokumen Anda adalah:

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

Maka permintaan Anda akan:

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

Ide yang menarik ... tetapi Anda biasanya ingin mengatur nilai _idyang diberikan, daripada membiarkan MongoDB menghasilkan yang lain.
Dan Dascalescu

0

Anda juga dapat membuat dokumen baru dari kompas MongoDB atau menggunakan perintah dan mengatur specific _idnilai yang Anda inginkan.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.