MongoDBのオブジェクトを部分的に更新して、新しいオブジェクトが既存のオブジェクトとオーバーレイ/マージされるようにする方法


183

このドキュメントをMongoDBに保存した場合

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

上の新しい情報を持つオブジェクトparam2param3外の世界からは、保存する必要があります

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

新しいフィールドをオブジェクトの既存の状態にマージ/オーバーレイして、param1が削除されないようにしたい

これを行う

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

MongoDBは要求されたとおりに動作し、some_keyをその値に設定します。古いものを交換してください。

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

MongoDBで新しいフィールドのみを更新する方法は何ですか(1つずつ明示的に指定せずに)?これを取得するには:

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

私はJavaクライアントを使用していますが、どんな例でもありがたいです


2
$ mergeの問題に投票してください:jira.mongodb.org/browse/SERVER-21094
Ali

回答:


107

質問を正しく理解した場合、別のドキュメントのコンテンツでドキュメントを更新しますが、まだ存在しないフィールドのみを更新し、(別の値に設定されている場合でも)すでに設定されているフィールドを完全に無視します。

単一のコマンドでそれを行う方法はありません。

最初にドキュメントをクエリし、必要なものを見つけてから$set更新する必要があります(古い値を一致フィルターとして使用して、間に同時更新がないことを確認します)。


あなたの質問の別の読みは、あなたはに満足している$setがすべてのフィールドを明示的に設定したくないということでしょう。次に、データをどのように渡しますか?

次のことを実行できます。

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

あなたは無条件にすべてのフィールドを設定したい場合は、使用することができます$マルチ:このように、パラメータを db.collection.update( { _id:...} , { $set: someObjectWithNewData, { $multi: true } } )
村人

12
$ multiパラメータはありません。マルチオプション(リンク先)がありますが、フィールドの更新方法には影響しません。単一のドキュメントを更新するか、クエリパラメータに一致するすべてのドキュメントを更新するかを制御します。
ボブ・カーンズ2016年

1
これはまったく意味がありません。mongoを処理する場合、更新がアトミック操作を持つことも推奨されます。最初にそれを読み取ると、他の誰かがデータを変更するたびにダーティーリードが発生します。また、mongoにはトランザクションがないため、喜んでデータが破損します。
froginvasion 2017年

@froginvasion:更新の一致フィルターとして古い値を使用することで、アトミックにすることができます。そのため、その間に競合する同時更新があった場合、更新は失敗します。その後、それを検出し、それに応じて行動できます。これは楽観的ロックと呼ばれます。しかし、そうです、それは適切にそれを実装するアプリケーションに依存します。Mongo自体にはトランザクションが組み込まれていません。
Thilo

これは、JavaScriptで2つのオブジェクトをマージするというより一般的な問題だと思います。IIRCアイデアは、新しいもののプロパティを古いものに単に割り当てることです
information_interchange

110

自分の機能で解決しました。ドキュメント内の指定されたフィールドを更新する場合は、明確に対処する必要があります。

例:

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

param2のみを更新する場合は、次のようにするのは誤りです。

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 /

あなたの応答が私を導くのを助けてくれてありがとう。私は、次のようにユーザーがコレクション内のユーザーの特定のキーの値を変更するエーブルされた: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.param2some_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"
    }
}

好きなだけ深く掘り下げることができます。これは、既存のプロパティに影響を与えずにオブジェクトに新しいプロパティを追加する場合にも役立ちます。


「some key」のような新しいキーを追加できますか:{"param1": "val1"、 "param2": "val2_new"、 "param3": "val3_new"、 "param4": "val4_new"、}
ウルヴァシ・ソーニ

17

最良の解決策は、オブジェクトからプロパティを抽出し、それらをフラットなドット表記のキーと値のペアにすることです。たとえば、次のライブラリを使用できます。

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

これは.flatten、既存のDBオブジェクトのプロパティが不要に削除/上書きされることを心配することなく、オブジェクトをフラットなプロパティセットに変更して、$ 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
  }
}
*/

そして、それは完璧なセレクターを形成します-それは与えられたプロパティだけを更新します。編集:私は時々考古学者になりたいです;)



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.2db.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" } }
  • 最初の部分{}は一致クエリで、更新するドキュメント(この場合はすべてのドキュメント)をフィルタリングします。

  • 2番目の部分[{ $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

$ setを使用してこのプロセスを実行します

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

0

必要なドットパスを含むプロパティ名で更新オブジェクトを作成します。(OPの例の「somekey。」+)、それを使用して更新を行います。

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

ええ、最善の方法は、このコメントで言及されているように、オブジェクト表記をフラットなKey-Value文字列表現に変換することです。 https //stackoverflow.com/a/39357531/2529199

このNPMライブラリを使用して別の方法を強調したかった:https : //www.npmjs.com/package/dot-objectを使用すると、ドット表記を使用してさまざまなオブジェクトを操作できます。

次のように、このパターンを使用して、Key-Valueを関数変数として受け入れるときに、ネストされたオブジェクトプロパティをプログラムで作成しました。

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

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

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

このパターンにより、ネストされたプロパティと単一レベルのプロパティを交換可能に使用して、Mongoにきれいに挿入できます。

Mongoだけでなく、特にクライアント側でJSオブジェクトをいじる必要があるが、Mongoでの作業に一貫性がある場合、このライブラリーは前述のmongo-dot-notationNPMモジュールよりも多くのオプションを提供します。

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では、Spread Operator(...)を使用して更新できます。

db.collection.update(  { _id:...} , { $set: { ...update  } } 
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.