MongoDB-ドキュメントの配列内のオブジェクトを更新する(ネストされた更新)


204

次のコレクションがあると仮定します。

{
    "_id" : ObjectId("4faaba123412d654fe83hg876"),
    "user_id" : 123456,
    "total" : 100,
    "items" : [
            {
                    "item_name" : "my_item_one",
                    "price" : 20
            },
            {
                    "item_name" : "my_item_two",
                    "price" : 50
            },
            {
                    "item_name" : "my_item_three",
                    "price" : 30
            }
    ]
}

1-「item_name」:「my_item_two」の価格を上げたいのです存在しない場合は、「items」配列に追加する必要があります。

2-2つのフィールドを同時に更新するにはどうすればよいですか。たとえば、「my_item_three」の価格を引き上げると同時に、「合計」を引き上げます(値は同じ)。

私はこれをMongoDB側で行うことを好みます。そうでない場合は、クライアント側(Python)でドキュメントをロードし、更新されたドキュメントを作成して、MongoDBの既存のドキュメントと置き換える必要があります。

更新 これは私が試したものであり、オブジェクトが存在する場合は正常に動作します:

db.test_invoice.update({user_id : 123456 , "items.item_name":"my_item_one"} , {$inc: {"items.$.price": 10}})

しかし、キーが存在しない場合は何もしません。また、ネストされたオブジェクトのみを更新します。このコマンドでは、「合計」フィールドも更新する方法はありません。


1
私はあなたがモンゴでこれを行うことはできないと思います。Mongoのデータ操作は非常に限られています。
Antti Haapala、2012年

3
@Haapala:MongoDBはアップサートと$株式会社とアップデートがある
JDI

1
@jdiはいはい、しかしここではあまり役に立ちませんが、条件付きで複数の$ incが必要であり、アイテムが存在しない場合は$ pushが必要です。
Antti Haapala、2012年

回答:


237

質問1では、2つの部分に分けましょう。まず、「items.item_name」が「my_item_two」と等しいドキュメントをインクリメントします。このためには、定位置 "$"演算子を使用する必要があります。何かのようなもの:

 db.bar.update( {user_id : 123456 , "items.item_name" : "my_item_two" } , 
                {$inc : {"items.$.price" : 1} } , 
                false , 
                true);

これは、配列内で最初に一致したサブドキュメントのみをインクリメントすることに注意してください(「item_name」が「my_item_two」と等しい別のドキュメントが配列内にある場合は、インクリメントされません)。しかし、これはあなたが望むものかもしれません。

2番目の部分はトリッキーです。次のように、「my_item_two」なしで新しい項目を配列にプッシュできます。

 db.bar.update( {user_id : 123456, "items.item_name" : {$ne : "my_item_two" }} , 
                {$addToSet : {"items" : {'item_name' : "my_item_two" , 'price' : 1 }} } ,
                false , 
                true);

質問#2の場合、答えは簡単です。「my_item_three」を含むドキュメントのitem_threeの合計と価格を増やすには、複数のフィールドで同時に$ inc演算子を使用できます。何かのようなもの:

db.bar.update( {"items.item_name" : {$ne : "my_item_three" }} ,
               {$inc : {total : 1 , "items.$.price" : 1}} ,
               false ,
               true);

返信いただきありがとうございます。質問2の答えは完璧で問題なく動作します:)しかし、質問1の問題は、「items.item_name」ではなく「user_id」でドキュメントを検索する必要があることです。この場合、検索クエリで配列を指定していないため、位置演算子を使用できません。また、両方のパーツを1つのショットで実行する必要があります。(可能な場合)
マジッド

いい例です。私は自分の答えのリンクからこの方向性を明らかにしているように感じましたが、彼の求めているものではないと言って抵抗を続けていました。OPは、複数の$ incを実行する方法の例を表示するために必要なだけだと思います。
jdi

質問#1の場合、各更新のクエリビットにuser_idを追加することで、2つの部分でそれを行うことができるはずです(それに応じて回答を変更します)。シングルショットで実行できるかどうかについてもっと考える必要があります。
matulef 2012年

6
updateメソッドの3番目と4番目のパラメーターは何ですか?なぜ?
skmahawar 2015

5
@skmahawarは、3番目と4番目のパラメータdocs.mongodb.com/manual/reference/method/db.collection.updateに関して、これらのオプションがそれぞれ「upsert」と「multi」用であることを示しています。アップサートの場合、trueに設定すると、クエリ基準に一致するドキュメントがない場合に新しいドキュメントが作成されます。デフォルト値はfalseで、一致が見つからない場合は新しいドキュメントを挿入しません。 "マルチの場合、trueに設定すると、クエリ条件を満たす複数のドキュメントが更新されます。falseに設定すると、1つのドキュメントが更新されます。デフォルト値は偽。
マイク・ストランド

24

単一のクエリでこれを行う方法はありません。最初のクエリでドキュメントを検索する必要があります。

ドキュメントが存在する場合:

db.bar.update( {user_id : 123456 , "items.item_name" : "my_item_two" } , 
                {$inc : {"items.$.price" : 1} } , 
                false , 
                true);

そうしないと

db.bar.update( {user_id : 123456 } , 
                {$addToSet : {"items" : {'item_name' : "my_item_two" , 'price' : 1 }} } ,
                false , 
                true);

条件を追加する必要はありません {$ne : "my_item_two" }

また、マルチスレッド環境では、2番目のスレッド(ドキュメントが見つからなかった場合は挿入)を一度に1つのスレッドしか実行できないことに注意する必要があります。そうしないと、重複した埋め込みドキュメントが挿入されます。


1
いいえ、単一のクエリでこれを行う方法があります。上記を参照してください
Martijn Scheffer

1
@MartijnScheffer、これをこのスレッドのどこかで単一のクエリとして実行する方法はわかりません。「回答」でも、この回答と同じ2つのクエリを使用しています。何か不足していますか?また、マルチスレッドの問題を防ぐために1つのクエリでこれを実行する方法があることを願っています
dferraro

@Udit、条件内でitems。$。priceをどのように使用できますか?「価格が0より大きい場合にのみインクリメントする」などの条件を追加しようとしても機能しません。基本的に何も更新されません。where条件でこれを使用できますか、それとも何か不足していますか?items。$。price:{$ gt:0}
dferraro

何かが消えたに違いない:)
Martijn Scheffer

3

$set演算子を使用して、オブジェクトフィールド内のネストされた配列を更新し、値を更新できます

db.getCollection('geolocations').update( 
   {
       "_id" : ObjectId("5bd3013ac714ea4959f80115"), 
       "geolocation.country" : "United States of America"
   }, 
   { $set: 
       {
           "geolocation.$.country" : "USA"
       } 
    }, 
   false,
   true
);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.