Як я частково оновлюю об’єкт у MongoDB, щоб новий об'єкт наклався / злився з існуючим


183

Враховуючи цей документ, збережений у MongoDB

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

Об'єкт з новою інформацією про param2та param3від зовнішнього світу потребує збереження

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

Я хочу об'єднати / накласти нові поля над існуючим станом об’єкта, щоб парам1 не видалявся

Роблячи це

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

Це призведе до того, що MongoDB робить саме так, як було запропоновано, і встановить some_key до цього значення. замінивши стару.

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

Який спосіб MongoDB оновлювати лише нові поля (без чіткого їх окреслення)? щоб отримати це:

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

Я використовую клієнт Java, але будь-який приклад буде вдячний


2
будь ласка, проголосуйте за проблему злиття $: jira.mongodb.org/browse/SERVER-21094
Алі

Відповіді:


107

Якщо я правильно розумію питання, ви хочете оновити документ із вмістом іншого документа, але лише поля, які вже відсутні, і повністю ігнорувати вже встановлені поля (навіть якщо до іншого значення).

Немає способу зробити це в одній команді.

Спершу потрібно запитати документ, з’ясувати, що ви хочете, $setа потім оновити його (використовуючи старі значення як фільтр відповідності, щоб переконатися, що між ними не отримуються одночасні оновлення).


Ще одним прочитанням вашого питання буде те, що ви задоволені $set, але не хочете чітко встановлювати всі поля. Як би ви тоді передали дані?

Ви знаєте, що можете зробити наступне:

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

Якщо ви хочете встановити всі поля , безумовно, ви можете використовувати мульти $ параметр, наприклад: db.collection.update( { _id:...} , { $set: someObjectWithNewData, { $multi: true } } )
Селянин

12
Параметр $ multi не існує. Є кілька варіантів (з якими ви пов’язані), але це не впливає на те, як оновлюються поля. Він контролює, чи оновлюєте ви один документ або всі документи, які відповідають параметру запиту.
Боб Кернс

1
Це не має жодного сенсу. Що стосується монго, то також бажано також оновити атомні операції. Перше читання викликає брудне читання, коли хтось інший змінює ваші дані. А оскільки у монго немає транзакцій, він із задоволенням зіпсує дані для вас.
froginvasion

@froginvasion: Ви можете зробити його атомним, використовуючи старі значення як фільтр відповідності для оновлення. Таким чином, ваше оновлення не вдасться, якщо тим часом відбулося суперечливе одночасне оновлення. Потім ви можете виявити це і діяти відповідно. Це називається оптимістичним блокуванням. Але так, це залежить від того, щоб ваша програма його правильно реалізувала, сам Монго не має вбудованих транзакцій.
Тіло

Я думаю, що це більш загальне питання об’єднання двох об’єктів у JavaScript. Ідея IIRC полягає в тому, щоб просто присвоїти властивості нового старому
information_interchange

110

Я вирішив це власною функцією. Якщо ви хочете оновити вказане поле в документі, вам потрібно чітко його вирішити.

Приклад:

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

Якщо ви хочете оновити лише пара2, це неправильно робити:

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

Ви повинні використовувати:

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

Тож я написав функцію щось подібне:

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
Це має бути прийнятою відповіддю. Для кращої функції flattenObject дивіться сторінку gist.github.com/penguinboy/762197
steph643

Дякую, що ваша відповідь допомогла мені направити. Я зміг змінити значення конкретного ключа користувача у колекції користувачів таким чином:db.users.update( { _id: ObjectId("594dbc3186bb5c84442949b1") }, { $set: { resume: { url: "http://i.imgur.com/UFX0yXs.jpg" } } } )
Люк Шоен

Чи є ця операція атомною і ізольованою? Значить, якщо 2 паралельних нитки намагаються встановити 2 різних поля одного і того ж документа, це призведе до стану гонки та непередбачуваного результату
так випадковий чувак

21

Ви можете використовувати крапки-нотації для доступу та встановлення полів глибоко всередині об'єктів, не впливаючи на інші властивості цих об'єктів.

Враховуючи вказаний вище об'єкт:

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

Ми можемо оновити просто some_key.param2та 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"
    }
}

Ви можете заглибитись так глибоко, як вам заманеться Це також корисно для додавання нових властивостей до об'єкта, не впливаючи на існуючі.


Чи можу я додати новий ключ ... як "деякий ключ": {"param1": "val1", "param2": "val2_new", "param3": "val3_new", "param4": "val4_new",}
Урваші Соні

17

Найкраще рішення - вилучити властивості з об’єкта та зробити їх парними ключами-значеннями пар-нотацій. Ви можете використовувати, наприклад, цю бібліотеку:

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

Він має .flattenфункцію, яка дозволяє вам змінити об'єкт на плоский набір властивостей, який потім може бути наданий модифікатору $ set, без побоювань, що будь-яке властивість вашого існуючого об'єкта БД буде видалено / перезаписано без потреби.

Взято з mongo-dot-notationдокументів:

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

І тоді він формує ідеальний селектор - оновить ТОЛЬКО задані властивості. EDIT: Мені подобається бути археологом кілька разів;)


10

Mongo дозволяє оновлювати вкладені документи за допомогою .конвенції. Погляньте: оновлення вкладених документів у mongodb . Ось ще одне попереднє запитання щодо оновлення злиття, як-от таке, яке ви шукаєте. Я вважаю: атомне оновлення MongoDB за допомогою документа «злиття».


6

Я мав успіх робити це так:

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

У мене є функція, яка динамічно обробляє оновлення мого профілю

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

Примітка: "профіль" є специфічним для моєї реалізації, це лише рядок ключа, який ви хочете змінити.



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

Якщо у вас є кілька полів для оновлення

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

1

Починаючи Mongo 4.2, db.collection.update()можна прийняти конвеєр агрегації, який дозволяє використовувати такі оператори агрегації, як $addFields, який виводить усі існуючі поля з вхідних документів та нових доданих полів:

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" } }
  • Перша частина {}- це запит на відповідність, фільтруючи документи, які потрібно оновити (в даному випадку всі документи).

  • Друга частина [{ $addFields: { some_key: new_info } }]- це оновлення конвеєра оновлення:

    • Зверніть увагу на квадратні дужки, що означають використання трубопроводу для агрегації.
    • Оскільки це конвеєрний конвеєр, ми можемо використовувати $addFields.
    • $addFields виконує саме те, що потрібно: оновлення об'єкта, щоб новий об’єкт наклався / злився з існуючим:
    • У цьому випадку { param2: "val2_new", param3: "val3_new" }буде об'єднано в існуюче some_key, залишившись param1недоторканим і або додайте, або замініть і те, param2і param3.
  • Не забувайте { multi: true }, інакше буде оновлено лише перший відповідний документ.


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

до

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

Сподіваюся, що це допоможе!


1

Ви можете скоріше зробити порив, ця операція в MongoDB використовується для збереження документа в колекцію. Якщо документ відповідає критеріям запиту, він виконає операцію оновлення, інакше він вставить новий документ у колекцію.

щось подібне, як нижче

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

0

використовувати цей набір, виконайте цей процес

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

0

Зробіть об’єкт оновлення з іменами властивостей, включаючи необхідний крапковий шлях. ("somekey". + з прикладу OP), а потім використовуйте це оновлення.

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

Так, найкращий спосіб - перетворити позначення об'єкта в плоське представлення рядка ключ-значення, про що йдеться в цьому коментарі: https://stackoverflow.com/a/39357531/2529199

Я хотів виділити альтернативний метод за допомогою цієї бібліотеки NPM: https://www.npmjs.com/package/dot-object який дозволяє вам маніпулювати різними об'єктами, використовуючи крапкові позначення.

Я використовував цю схему для програмного створення властивості вкладеного об'єкта, приймаючи ключ-значення як змінну функції:

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

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

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

Цей зразок дозволяє мені взаємозамінно використовувати вкладені, а також однорівневі властивості та чітко вставляти їх у Монго.

Якщо вам потрібно пограти з JS-об'єктами поза монго, особливо на стороні клієнта, але послідовність під час роботи з Mongo, ця бібліотека дає вам більше варіантів, ніж згаданий раніше mongo-dot-notationмодуль NPM.

PS Спочатку я хотів згадати це як коментар, але, мабуть, мій репортер S / O недостатньо високий, щоб написати коментар. Отже, не намагаючись зауважити коментар SzybkiSasza, просто хотілося виділити надання альтернативного модуля.



0

Вам слід подумати над тим, щоб оновити об'єкт взаємозамінно, а потім просто зберегти об'єкт оновленими полями. Щось подібне зроблено нижче

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

Ви повинні використовувати вбудовані документи (впорядкувати об’єкт шляху)

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

Отже, в JavaScript ви можете використовувати Opead Spread (...) для оновлення

db.collection.update(  { _id:...} , { $set: { ...update  } } 
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.