Як я частково оновлюю об’єкт у 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.