MongoDB'de bir nesneyi kısmen nasıl güncelleyebilirim, böylece yeni nesne mevcut nesneyi kaplar / birleştirir


183

MongoDB'ye kaydedilen bu belge göz önüne alındığında

{
   _id : ...,
   some_key: { 
        param1 : "val1",
        param2 : "val2",
        param3 : "val3"
   }
}

Üzerinde yeni bilgilerle bir nesne param2ve param3dış dünyadan kaydedilmesi gerekiyor

var new_info = {
    param2 : "val2_new",
    param3 : "val3_new"
};

Param1 kaldırılmaması için yeni alanları nesnenin mevcut durumu üzerine birleştirmek / üzerine yerleştirmek istiyorum

Bunu yapmak

db.collection.update(  { _id:...} , { $set: { some_key : new_info  } } 

MongoDB, tam olarak istendiği gibi yapıyor ve some_key değerini bu değere ayarlar. eskisinin yerine.

{
   _id : ...,
   some_key: { 
      param2 : "val2_new",
      param3 : "val3_new"
   }
}

MongoDB'nin yalnızca yeni alanları güncellemesinin (bunları açıkça tek tek belirtmeden) yolu nedir? bunu elde etmek için:

{
   _id : ...,
   some_key: { 
        param1 : "val1",
        param2 : "val2_new",
        param3 : "val3_new"
   }
}

Java istemcisini kullanıyorum, ancak herhangi bir örnek takdir edilecektir


2
lütfen $ birleştirme sorununa oy verin: jira.mongodb.org/browse/SERVER-21094
Ali

Yanıtlar:


107

Soruyu doğru anlarsam, bir belgeyi başka bir belgenin içeriğiyle güncellemek, ancak yalnızca mevcut olmayan alanları güncellemek ve önceden ayarlanmış alanları (başka bir değere bile olsa) tamamen yok saymak istersiniz.

Bunu tek bir komutla yapmanın bir yolu yok.

Önce belgeyi sorgulamanız, ne yapmak istediğinizi bulmanız $setve ardından güncellemeniz gerekir (aralarında eşzamanlı güncellemeler almadığınızdan emin olmak için eski değerleri eşleşen bir filtre olarak kullanarak).


Sorunuzun başka bir okuması, memnun olduğunuz $set, ancak tüm alanları açıkça ayarlamak istemediğiniz olacaktır. O halde verileri nasıl iletirsiniz?

Aşağıdakileri yapabileceğinizi biliyorsunuz:

db.collection.update(  { _id:...} , { $set: someObjectWithNewData } 

Tüm alanları koşulsuz olarak ayarlamak isterseniz, $ multi parametresini şu şekilde kullanabilirsiniz : db.collection.update( { _id:...} , { $set: someObjectWithNewData, { $multi: true } } )
Köylü

12
$ Multi parametresi yok. Çok seçenek var (bağlandığınız şey bu), ancak alanların nasıl güncelleneceğini etkilemez. Tek bir belgeyi veya sorgu parametresiyle eşleşen tüm belgeleri güncelleştirip güncellemediğinizi denetler.
Bob Kerns

1
Bu hiç mantıklı değil. Mongo ile uğraşırken, güncellemelerin atomik operasyonlara sahip olması da tercih edilir. İlk okuma, başkası verilerinizi değiştirdiğinde kirli bir okumaya neden olur. Ve mongo'nun hiçbir işlemi olmadığından, verileri sizin için mutlu bir şekilde bozacaktır.
froginvasion

@froginvasion: Eski değerleri güncellemede eşleşen bir filtre olarak kullanarak atom haline getirebilirsiniz.Bu arada, çakışan bir eşzamanlı güncelleme varsa güncellemeniz başarısız olur. Daha sonra bunu tespit edebilir ve buna göre hareket edebilirsiniz. Buna iyimser kilitleme denir. Ancak evet, uygulamanızın doğru şekilde uygulanmasına bağlıdır, Mongo'nun yerleşik işlemleri yoktur.
Thilo

Bu, javascript'te iki nesnenin birleştirilmesi ile ilgili daha genel bir soru olduğunu düşünüyorum. IIRC fikri basitçe eskiye birinin özelliklerini atamaktır
information_interchange

110

Kendi fonksiyonumla çözdüm. Belgedeki belirtilen alanı güncellemek istiyorsanız, alanı net bir şekilde ele almanız gerekir.

Misal:

{
    _id : ...,
    some_key: { 
        param1 : "val1",
        param2 : "val2",
        param3 : "val3"
    }
}

Yalnızca param2'yi güncellemek istiyorsanız, aşağıdakileri yapmak yanlıştır:

db.collection.update(  { _id:...} , { $set: { some_key : new_info  } }  //WRONG

Kullanmalısın:

db.collection.update(  { _id:...} , { $set: { some_key.param2 : new_info  } } 

Bu yüzden böyle bir işlev yazdım:

function _update($id, $data, $options=array()){

    $temp = array();
    foreach($data as $key => $value)
    {
        $temp["some_key.".$key] = $value;
    } 

    $collection->update(
        array('_id' => $id),
        array('$set' => $temp)
    );

}

_update('1', array('param2' => 'some data'));

11
Bu kabul edilen cevap olmalı. Daha iyi bir düzleştirme işlevi için, bkz. Gist.github.com/penguinboy/762197
steph643

Yanıtınız bana yardımcı oldu, teşekkürler. Kullanıcı koleksiyonundaki bir kullanıcının belirli bir anahtarının değerini şu şekilde değiştirdim:db.users.update( { _id: ObjectId("594dbc3186bb5c84442949b1") }, { $set: { resume: { url: "http://i.imgur.com/UFX0yXs.jpg" } } } )
Luke Schoen

Bu operasyon atomik ve izole midir? Yani, 2 paralel iş parçacığı aynı belgenin 2 farklı alanını ayarlamaya çalışırsa, bu durum yarış durumu ve öngörülemeyen sonuç ile sonuçlanır
so-random-dude

21

Nokta gösterimini, nesnelerin diğer özelliklerini etkilemeden nesnelerin içindeki alanlara erişmek ve ayarlamak için kullanabilirsiniz.

Yukarıda belirttiğiniz nesne göz önüne alındığında:

> db.test.insert({"id": "test_object", "some_key": {"param1": "val1", "param2": "val2", "param3": "val3"}})
WriteResult({ "nInserted" : 1 })

Biz sadece güncelleyebilirsiniz some_key.param2ve some_key.param3:

> db.test.findAndModify({
... query: {"id": "test_object"},
... update: {"$set": {"some_key.param2": "val2_new", "some_key.param3": "val3_new"}},
... new: true
... })
{
    "_id" : ObjectId("56476e04e5f19d86ece5b81d"),
    "id" : "test_object",
    "some_key" : {
        "param1" : "val1",
        "param2" : "val2_new",
        "param3" : "val3_new"
    }
}

İstediğiniz kadar derine inebilirsiniz. Bu, varolanları etkilemeden bir nesneye yeni özellikler eklemek için de yararlıdır.


Yeni bir anahtar ekleyebilir miyim ... "bazı anahtar" gibi: {"param1": "val1", "param2": "val2_new", "param3": "val3_new", "param4": "val4_new",}
Urvashi Soni

17

En iyi çözüm, nesneden özellikleri ayıklamak ve bunları düz nokta gösterimi anahtar / değer çiftleri yapmaktır. Örneğin bu kütüphaneyi kullanabilirsiniz:

https://www.npmjs.com/package/mongo-dot-notation

Vardır .flattenMevcut DB nesnesinin herhangi bir özelliğinin gerekmeksizin silineceği / üzerine yazılacağı endişesi olmadan, $ set değiştiricisine verilebilecek düz özellikler kümesine değiştirmenizi sağlayan bir işleve .

Alındığı mongo-dot-notationdocs:

var person = {
  firstName: 'John',
  lastName: 'Doe',
  address: {
    city: 'NY',
    street: 'Eighth Avenu',
    number: 123
  }
};



var instructions = dot.flatten(person)
console.log(instructions);
/* 
{
  $set: {
    'firstName': 'John',
    'lastName': 'Doe',
    'address.city': 'NY',
    'address.street': 'Eighth Avenu',
    'address.number': 123
  }
}
*/

Ve sonra mükemmel bir seçici oluşturur - SADECE verilen özellikleri güncelleyecektir. DÜZENLEME: Bazı zamanlarda arkeolog olmayı seviyorum;)



6

Bu şekilde başarılı oldum:

db.collection.update(  { _id:...} , { $set: { 'key.another_key' : new_info  } } );

Profil güncellemelerimi dinamik olarak işleyen bir fonksiyonum var

function update(prop, newVal) {
  const str = `profile.${prop}`;
  db.collection.update( { _id:...}, { $set: { [str]: newVal } } );
}

Not: 'Profil' benim uygulamama özeldir, sadece değiştirmek istediğiniz anahtarın dizesidir.



1
    // where clause DBObject
    DBObject query = new BasicDBObject("_id", new ObjectId(id));

    // modifications to be applied
    DBObject update = new BasicDBObject();

    // set new values
    update.put("$set", new BasicDBObject("param2","value2"));

   // update the document
    collection.update(query, update, true, false); //3rd param->upsertFlag, 4th param->updateMultiFlag

Güncellenecek birden fazla alanınız varsa

        Document doc = new Document();
        doc.put("param2","value2");
        doc.put("param3","value3");
        update.put("$set", doc);

1

Başlangıç Mongo 4.2, db.collection.update()örneğin toplama operatörleri kullanarak sağlayan bir toplama boru hattı, kabul edebilir $addFieldsgiriş belgeleri ve yeni eklenen alanlardan mevcut tüm alanları verir:

var new_info = { param2: "val2_new", param3: "val3_new" }

// { some_key: { param1: "val1", param2: "val2", param3: "val3" } }
// { some_key: { param1: "val1", param2: "val2"                 } }
db.collection.update({}, [{ $addFields: { some_key: new_info } }], { multi: true })
// { some_key: { param1: "val1", param2: "val2_new", param3: "val3_new" } }
// { some_key: { param1: "val1", param2: "val2_new", param3: "val3_new" } }
  • İlk bölüm {}hangi belgelerin güncelleneceğini filtreleyen eşleşme sorgusudur (bu durumda tüm belgeler).

  • İkinci bölüm [{ $addFields: { some_key: new_info } }], güncelleme toplama boru hattıdır:

    • Birleştirme boru hattının kullanımını belirten köşeli parantezlere dikkat edin.
    • Bu bir toplama hattı olduğu için kullanabiliriz $addFields.
    • $addFields tam olarak ihtiyacınız olanı gerçekleştirir: yeni nesnenin var olanla örtüşmesi / birleşmesi için nesnenin güncellenmesi:
    • Bu durumda, { param2: "val2_new", param3: "val3_new" }mevcut birleştirilecek olacak some_keytutarak param1bakir ve ya eklemek veya her ikisinin yerine param2ve param3.
  • Unutmayın { multi: true }, aksi takdirde yalnızca eşleşen ilk belge güncellenir.


1
db.collection.update(  { _id:...} , { $set: { some_key : new_info  } } 

için

db.collection.update( { _id: ..} , { $set: { some_key: { param1: newValue} } } ); 

Umarım bu yardım!


1

Bir upert yapmayı tercih edebilirsiniz, MongoDB'deki bu işlem, belgeyi koleksiyona kaydetmek için kullanılır. Belge sorgu ölçütleriyle eşleşiyorsa güncelleme işlemini gerçekleştirir, aksi takdirde koleksiyona yeni bir belge ekler.

aşağıdaki gibi bir şey

db.employees.update(
    {type:"FT"},
    {$set:{salary:200000}},
    {upsert : true}
 )

0

$ set kullan bu işlemi yap

.update({"_id": args.dashboardId, "viewData._id": widgetId}, {$set: {"viewData.$.widgetData": widgetDoc.widgetData}})
.exec()
.then(dashboardDoc => {
    return {
        result: dashboardDoc
    };
}); 

0

Gerekli nokta yolunu içeren özellik adlarını içeren bir güncelleme nesnesi oluşturun. (OP örneğinden "somekey." +) yazın ve ardından güncellemeyi yapın.

//the modification that's requested
updateReq = { 
   param2 : "val2_new",
   param3 : "val3_new"   
}

//build a renamed version of the update request
var update = {};
for(var field in updateReq){
    update["somekey."+field] = updateReq[field];
}

//apply the update without modifying fields not originally in the update
db.collection.update({._id:...},{$set:update},{upsert:true},function(err,result){...});

0

Evet, en iyi yol, nesne yorumunu bu açıklamada belirtildiği gibi düz bir anahtar / değer dizesi temsiline dönüştürmektir: https://stackoverflow.com/a/39357531/2529199

Bu NPM kütüphanesini kullanarak alternatif bir yöntemi vurgulamak istedim: https://www.npmjs.com/package/dot-object Nokta gösterimini kullanarak farklı nesneleri değiştirmenize izin verir.

Anahtar / değer bir işlev değişkeni olarak kabul ederken, programlama yoluyla bir iç içe nesne özelliği oluşturmak için bu deseni kullandım:

const dot = require('dot-object');

function(docid, varname, varvalue){
  let doc = dot.dot({
      [varname]: varvalue 
  });

  Mongo.update({_id:docid},{$set:doc});
}

Bu desen, iç içe geçmiş ve tek düzey özellikleri birbirinin yerine kullanmamı ve Mongo'ya temiz bir şekilde eklememi sağlar.

Yalnızca istemci tarafında Mongo'nun ötesinde JS Objects ile oynamanız gerekiyorsa, ancak Mongo ile çalışırken tutarlılığınız varsa, bu kütüphane size daha önce belirtilen mongo-dot-notationNPM modülünden daha fazla seçenek sunar .

PS Ben aslında sadece bir yorum olarak bu söz istedim ama görünüşe göre benim S / O temsilcisi yorum göndermek için yeterince yüksek değil. Yani, SzybkiSasza'nın yorumunda kas kurmaya çalışmamak, sadece alternatif bir modül sağlamayı vurgulamak istedi.



0

Nesneyi dönüşümlü olarak güncellemeyi düşünmeli ve daha sonra nesneyi güncellenmiş alanlarla depolamalısınız. Aşağıdaki gibi bir şey

function update(_id) {
    return new Promise((resolve, reject) => {

        ObjModel.findOne({_id}).exec((err, obj) => {

            if(err) return reject(err)

            obj = updateObject(obj, { 
                some_key: {
                    param2 : "val2_new",
                    param3 : "val3_new"
                }
            })

            obj.save((err, obj) => {
                if(err) return reject(err)
                resolve(obj)
            })
        })
    })
}


function updateObject(obj, data) {

    let keys = Object.keys(data)

    keys.forEach(key => {

        if(!obj[key]) obj[key] = data[key]

        if(typeof data[key] == 'object') 
            obj[key] = updateObject(obj[key], data[key])
        else
            obj[key] = data[key]
    })

    return obj
}

0

Gömülü Belgeleri kullanmanız gerekir (yol nesnesinin dizgisi)

let update = {}
Object.getOwnPropertyNames(new_info).forEach(param => {
   update['some_key.' + param] = new_info[param]
})

Ve böylece, JavaScript'te güncelleme yapmak için Spread Operator (...) kullanabilirsiniz.

db.collection.update(  { _id:...} , { $set: { ...update  } } 
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.