Bagaimana cara saya memperbarui sebagian objek di MongoDB sehingga objek baru akan overlay / bergabung dengan yang sudah ada

183

Mengingat dokumen ini disimpan di MongoDB

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

Objek dengan informasi baru tentang param2dan param3dari dunia luar perlu diselamatkan

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

Saya ingin menggabungkan / overlay bidang baru di atas keadaan objek yang ada sehingga param1 tidak dihapus

Melakukan ini

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

Akan mengarah ke MongoDB melakukan persis seperti yang diminta, dan menetapkan some_key ke nilai itu. mengganti yang lama.

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

Apa cara agar MongoDB hanya memperbarui bidang baru (tanpa menyatakannya satu per satu secara eksplisit)? untuk mendapatkan ini:

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

Saya menggunakan klien Java, tetapi contoh apa pun akan dihargai

Eran Medan
sumber
2
silakan pilih isu $ merge: jira.mongodb.org/browse/SERVER-21094
Ali

Jawaban:

107

Jika saya memahami pertanyaan dengan benar, Anda ingin memperbarui dokumen dengan konten dokumen lain, tetapi hanya bidang yang belum ada, dan abaikan bidang yang sudah disetel (bahkan jika ke nilai lain).

Tidak ada cara untuk melakukan itu dalam satu perintah.

Anda harus menanyakan dokumen terlebih dahulu, mencari tahu apa yang Anda inginkan $setdan kemudian memperbaruinya (menggunakan nilai-nilai lama sebagai filter yang cocok untuk memastikan Anda tidak mendapatkan pembaruan bersamaan di antaranya).


Pembacaan lain dari pertanyaan Anda adalah Anda senang $set, tetapi tidak ingin secara eksplisit mengatur semua bidang. Bagaimana Anda mengirimkan data?

Anda tahu Anda dapat melakukan hal berikut:

db.collection.update(  { _id:...} , { $set: someObjectWithNewData } 
Thilo
sumber
Jika Anda ingin mengatur semua bidang tanpa syarat, Anda dapat menggunakan $ multi parameter, seperti ini: db.collection.update( { _id:...} , { $set: someObjectWithNewData, { $multi: true } } )
Villager
12
Tidak ada $ multi parameter. Ada opsi multi (yang Anda tautkan), tetapi itu tidak memengaruhi cara bidang diperbarui. Ini mengontrol apakah Anda memperbarui satu dokumen, atau semua dokumen yang cocok dengan parameter kueri.
Bob Kerns
1
Ini sama sekali tidak masuk akal. Ketika berhadapan dengan mongo, lebih disukai pembaruan untuk memiliki operasi atom. Pembacaan pertama itu menyebabkan pembacaan kotor setiap kali orang lain mengubah data Anda. Dan karena mongo tidak memiliki transaksi, itu dengan senang hati akan merusak data untuk Anda.
froginvasion
@froginvasion: Anda dapat membuatnya atomik dengan menggunakan nilai-nilai lama sebagai filter yang cocok pada pembaruan. Dengan begitu, pembaruan Anda akan gagal jika ada pembaruan bersamaan yang saling bertentangan. Anda kemudian dapat mendeteksi itu dan bertindak sesuai. Ini disebut penguncian optimis. Tapi ya, itu tergantung pada aplikasi Anda menerapkannya dengan benar, Mongo itu sendiri tidak memiliki transaksi bawaan.
Thilo
Saya pikir ini adalah pertanyaan yang lebih umum untuk menggabungkan dua objek dalam javascript. IIRC idenya adalah dengan hanya menetapkan properti yang baru ke yang lama
information_interchange
110

Saya menyelesaikannya dengan fungsi saya sendiri. Jika Anda ingin memperbarui bidang yang ditentukan dalam dokumen, Anda harus mengatasinya dengan jelas.

Contoh:

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

Jika Anda ingin memperbarui param2 saja, itu salah untuk dilakukan:

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

Anda harus menggunakan:

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

Jadi saya menulis fungsi seperti itu:

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'));
mehmatrix
sumber
11
Ini harus menjadi jawaban yang diterima. Untuk fungsi flattenObject yang lebih baik, lihat gist.github.com/penguinboy/762197
steph643
Terima kasih atas tanggapan Anda membantu membimbing saya. Saya dapat mengubah nilai kunci tertentu dari pengguna dalam koleksi pengguna sebagai berikut:db.users.update( { _id: ObjectId("594dbc3186bb5c84442949b1") }, { $set: { resume: { url: "http://i.imgur.com/UFX0yXs.jpg" } } } )
Luke Schoen
Apakah operasi ini atomik dan terisolasi? Berarti, jika 2 utas paralel mencoba mengatur 2 bidang berbeda pada dokumen yang sama, apakah akan menghasilkan kondisi balapan dan hasil yang tidak dapat diprediksi
jadi-acak-Bung
21

Anda dapat menggunakan notasi titik untuk mengakses dan mengatur bidang di dalam objek, tanpa memengaruhi properti lain dari objek tersebut.

Diberikan objek yang Anda tentukan di atas:

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

Kami dapat memperbarui secara adil some_key.param2dan 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"
    }
}

Anda dapat mempelajari sedalam yang Anda inginkan. Ini juga berguna untuk menambahkan properti baru ke objek tanpa mempengaruhi yang sudah ada.

Samir Talwar
sumber
Dapatkah saya menambahkan kunci baru ... seperti "beberapa kunci": {"param1": "val1", "param2": "val2_new", "param3": "val3_new", "param4": "val4_new",}
Urvashi Soni
17

Solusi terbaik adalah mengekstraksi properti dari objek dan menjadikannya pasangan nilai kunci dot-notasi datar. Anda dapat menggunakan misalnya perpustakaan ini:

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

Memiliki .flatten fungsi yang memungkinkan Anda untuk mengubah objek menjadi properti set datar yang kemudian dapat diberikan kepada $ set modifier, tanpa khawatir properti properti objek DB Anda yang ada akan dihapus / ditimpa tanpa perlu.

Diambil dari mongo-dot-notationdokumen:

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
  }
}
*/

Dan kemudian itu membentuk pemilih sempurna - itu akan memperbarui HANYA properti yang diberikan. EDIT: Saya suka menjadi arkeolog beberapa kali;)

SzybkiSasza
sumber
6

Saya berhasil melakukannya dengan cara ini:

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

Saya memiliki fungsi yang menangani pembaruan profil saya secara dinamis

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

Catatan: 'profil' khusus untuk implementasi saya, hanya string kunci yang ingin Anda modifikasi.

Matt Smith
sumber
1

Sepertinya Anda dapat mengatur isPartialObject yang dapat mencapai apa yang Anda inginkan.

Patrick Tescher
sumber
mencobanya. itu tidak memungkinkan Anda menyelamatkan sebagian objek jika saya tidak salah ... tapi terima kasih
Eran Medan
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

Jika Anda memiliki beberapa bidang yang akan diperbarui

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

Mulai Mongo 4.2, db.collection.update()dapat menerima pipa agregasi, yang memungkinkan menggunakan operator agregasi seperti $addFields, yang menampilkan semua bidang yang ada dari dokumen input dan bidang yang baru ditambahkan:

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" } }
  • Bagian pertama {}adalah kueri kecocokan, memfilter dokumen mana yang akan diperbarui (dalam hal ini semua dokumen).

  • Bagian kedua [{ $addFields: { some_key: new_info } }]adalah pipa agregasi pembaruan:

    • Perhatikan tanda kurung kotak yang menandakan penggunaan pipa agregasi.
    • Karena ini adalah pipa agregasi, kita bisa menggunakan $addFields.
    • $addFields melakukan persis apa yang Anda butuhkan: memperbarui objek sehingga objek baru akan overlay / bergabung dengan yang sudah ada:
    • Dalam hal ini, { param2: "val2_new", param3: "val3_new" }akan digabung menjadi yang sudah ada some_keydengan tetap param1tidak tersentuh dan menambah atau mengganti keduanya param2dan param3.
  • Jangan lupa { multi: true }, jika tidak , hanya dokumen yang cocok pertama yang akan diperbarui.

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

untuk

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

Semoga bantuan ini!

nguyenthang338
sumber
1

Anda bisa melakukan upert, operasi ini di MongoDB digunakan untuk menyimpan dokumen ke dalam koleksi. Jika dokumen cocok dengan kriteria kueri, maka ia akan melakukan operasi pembaruan jika tidak akan memasukkan dokumen baru ke dalam koleksi.

sesuatu yang mirip seperti di bawah ini

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

gunakan $ set lakukan proses ini

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

Buat objek pembaruan dengan nama properti termasuk jalur titik yang diperlukan. ("somekey." + dari contoh OP), lalu gunakan yang melakukan pembaruan.

//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){...});
Andrew
sumber
0

Ya, cara terbaik adalah mengubah notasi objek menjadi representasi string nilai kunci datar, seperti yang disebutkan dalam komentar ini: https://stackoverflow.com/a/39357531/2529199

Saya ingin menyoroti metode alternatif menggunakan perpustakaan NPM ini: https://www.npmjs.com/package/dot-object yang memungkinkan Anda memanipulasi objek yang berbeda menggunakan notasi titik.

Saya menggunakan pola ini untuk secara terprogram membuat properti objek bersarang saat menerima nilai kunci sebagai variabel fungsi, sebagai berikut:

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

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

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

Pola ini memungkinkan saya menggunakan properti bersarang dan tingkat tunggal secara bergantian, dan menyisipkannya dengan bersih ke dalam Mongo.

Jika Anda perlu bermain-main dengan JS Objects di luar hanya Mongo, terutama di sisi klien tetapi memiliki konsistensi ketika bekerja dengan Mongo, perpustakaan ini memberi Anda lebih banyak pilihan daripada mongo-dot-notationmodul NPM yang disebutkan sebelumnya .

PS Saya awalnya hanya ingin menyebutkan ini sebagai komentar tetapi ternyata S / O perwakilan saya tidak cukup tinggi untuk mengirim komentar. Jadi, tidak berusaha mengomentari komentar SzybkiSasza, hanya ingin menyoroti menyediakan modul alternatif.

Ashwin Shankar
sumber
0

Anda harus berpikir tentang memperbarui objek secara bergantian dan kemudian hanya menyimpan objek dengan bidang yang diperbarui. Sesuatu seperti dilakukan di bawah ini

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
}
Ivan Bravo Mendoza
sumber
0

Anda harus menggunakan Dokumen Tertanam (stringfy the path object)

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

Jadi, dalam JavaScript Anda dapat menggunakan Penyebar Operator (...) untuk memperbarui

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