Bir MongoDB Belgesinin _id'i nasıl güncellenir?


129

_idBir belgenin alanını güncellemek istiyorum . Bunun gerçekten iyi bir uygulama olmadığını biliyorum. Ancak bazı teknik nedenlerle onu güncellemem gerekiyor. Güncellemeye çalışırsam şunu alırım:

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

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

Ve güncelleme yapılmadı. Nasıl güncelleyebilirim?

Yanıtlar:


216

Onu güncelleyemezsin. Belgeyi _idyenisini kullanarak kaydetmeniz ve ardından eski belgeyi kaldırmanız gerekir.

// 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
Bu belgedeki bazı alanların benzersiz bir dizini varsa bununla ilgili eğlenceli bir sorun ortaya çıkar. Bu durumda, örneğiniz başarısız olur çünkü benzersiz bir dizine alınmış alana yinelenen bir değerle bir belge eklenemez. Bunu önce silme işlemini yaparak düzeltebilirsiniz, ancak bu kötü bir fikirdir çünkü ekiniz herhangi bir nedenle başarısız olursa verileriniz artık kaybolur. Bunun yerine dizini bırakmalı, işi yapmalı ve ardından dizini geri yüklemelisiniz.
skelly

İyi nokta @skelly! Benzer sorunları düşünmeye başladım ve yeni yorumunuzun sadece 2 saat önce yapıldığını gördüm. Öyleyse, bu değiştirici boşta kalma sorunu, kullanıcının kimliği seçmesine izin vermenin neden olduğu içsel bir sorun olarak kabul edilir.
RayLuo

1
Eğer bir alırsanız duplicate key erroriçinde inserthat ve @skelly bahsedilen sorun hakkında endişeli değildir, basit çözüm sadece aramak için removeilk satırı ve daha sonra çağrı inserthattını. docBasit belgeler için, insert başarısız olsa bile, en kötü durumda kurtarmak için kolay olurdu böylece zaten ekranda basılmış olmalıdır.
philfreo

Parametre olarak dize olmadan yalnızca ObjectId () kullanmak yeni bir benzersiz tane oluşturacaktır.
Erik

1
@ShankhadeepGhoshal evet, bu bir risk, özellikle de bunu canlı bir prodüksiyon sistemine karşı yapıyorsanız. Maalesef en iyi seçeneğinizin planlı bir kesinti yapmak ve bu süreçte tüm yazarları durdurmak olduğunu düşünüyorum. Biraz daha az acı verici olabilecek başka bir seçenek, uygulamaları geçici olarak salt okunur moda zorlamak olacaktır. DB'ye yazan tüm uygulamaları yalnızca ikincil bir düğüme işaret edecek şekilde yeniden yapılandırın. Okumalar başarılı olacak ancak bu süre zarfında yazmalar başarısız olacak ve DB'niz statik kalacaktır.
skelly

32

Tüm koleksiyonunuz için bunu yapmak için ayrıca bir döngü de kullanabilirsiniz (Niels örneğine göre):

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

Bu durumda, UserId kullanmak istediğim yeni kimlikti


1
ForEach öğesinin yinelenirken yanlışlıkla yeni dokümanlar almasını önlemek için find üzerindeki bir anlık görüntü () önerilir mi?
John Flinchbaugh

Bu kod parçacığı asla tamamlanmaz. Koleksiyonda sonsuza kadar yinelemeye devam ediyor. Anlık görüntü beklediğiniz şeyi yapmıyor (bir 'anlık görüntü' alarak koleksiyona bir belge ekleyerek ve ardından bu yeni belgenin anlık görüntüde olduğunu görerek test edebilirsiniz)
Patrick

Anlık görüntüye bir alternatif için stackoverflow.com/a/28083980/305324 adresine bakın . list()mantıklı olanıdır, ancak büyük veritabanları için bu hafızayı tüketebilir
Patrick

4
Bunun 11 olumlu oyu var ama biri bunun sonsuz bir döngü olduğunu söylüyor? Burada anlaşma nedir?
Andrew

4
@Andrew, çünkü çağdaş pop-coder kültürü, söz konusu girdinin gerçekten işe yaradığını doğrulamadan önce her zaman iyi girdiyi kabul etmeniz gerektiğini dikte ediyor.
csvan

5

Bu durumda, aynı koleksiyonda _id adını değiştirmek istersiniz (örneğin, bazı _id'lerin önüne önek eklemek isterseniz):

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) {... sonsuz döngüyü önlemek için gereklidir, çünkü forEach eklenen belgeleri seçer, hatta .snapshot () yöntemi kullanılır.


find(...).snapshot is not a functionama bunun dışında harika bir çözüm. Ayrıca, _idözel kimliğinizle değiştirmek isterseniz doc._id.toString().length === 24, sonsuz döngüyü engelleyip engellemeyeceğinizi kontrol edebilirsiniz (özel kimliklerinizin de 24 karakter uzunluğunda olmadığını varsayarak ),
Dan Dascalescu

1

Burada, döngüler ve eski belgelerin kaldırılması için birden çok isteği önleyen bir çözümüm var.

Aşağıdaki gibi bir şeyi kullanarak manuel olarak kolayca yeni bir fikir oluşturabilirsiniz: _id:ObjectId() Ancak, Mongo'nun eksik olması durumunda otomatik olarak bir _id atayacağını bilerek $project, belgenizin tüm alanlarını içeren bir toplama oluşturmak için kümeyi kullanabilirsiniz , ancak _id alanını atlayabilirsiniz. Daha sonra şununla kaydedebilirsiniz:$out

Yani belgeniz:

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

Ardından sorgunuz şöyle olacaktır:

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

İlginç bir fikir ... ancak _idMongoDB'nin başka bir tane oluşturmasına izin vermek yerine, genellikle belirli bir değere ayarlamak istersiniz .
Dan Dascalescu

0

Ayrıca MongoDB pusulasından veya komut kullanarak yeni bir belge oluşturabilir ve specific _idistediğiniz değeri ayarlayabilirsiniz .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.