221

要素の配列を保持するMongoドキュメントがあります。

=XX.handledの配列内のすべてのオブジェクトの属性をリセットしたいと思います。.profile

ドキュメントは次の形式です。

{
    "_id": ObjectId("4d2d8deff4e6c1d71fc29a07"),
    "user_id": "714638ba-2e08-2168-2b99-00002f3d43c0",
    "events": [{
            "handled": 1,
            "profile": 10,
            "data": "....."
        } {
            "handled": 1,
            "profile": 10,
            "data": "....."
        } {
            "handled": 1,
            "profile": 20,
            "data": "....."
        }
        ...
    ]
}

だから、私は次のことを試しました:

.update({"events.profile":10},{$set:{"events.$.handled":0}},false,true)

ただし、各ドキュメントで最初に一致した配列要素のみが更新されます。(これは、 $の定義された動作です-位置演算子です。)

一致したすべての配列要素を更新するにはどうすればよいですか?

4

15 に答える 15

118

更新: Mongo バージョン 3.6 の時点で、前述の問題が修正され、これを達成する方法があるため、この回答は有効ではなくなりました。他の回答を確認してください。


現時点では、位置演算子を使用して配列内のすべての項目を更新することはできません。JIRA http://jira.mongodb.org/browse/SERVER-1243を参照してください。

回避策として、次のことができます。

于 2011-01-12T14:09:43.670 に答える
74

私のために働いたのはこれでした:

db.collection.find({ _id: ObjectId('4d2d8deff4e6c1d71fc29a07') })
  .forEach(function (doc) {
    doc.events.forEach(function (event) {
      if (event.profile === 10) {
        event.handled=0;
      }
    });
    db.collection.save(doc);
  });

mongo の初心者や、JQuery や友人に精通している人にとっては、より明確だと思います。

于 2013-12-15T23:32:24.570 に答える
17

これは、まだ更新されていないサブドキュメントがあるドキュメントが残っているかどうかをチェックする while ループでも実現できます。この方法では、更新の原子性が保持されます (ここにある他の多くのソリューションでは保持されません)。

var query = {
    events: {
        $elemMatch: {
            profile: 10,
            handled: { $ne: 0 }
        }
    }
};

while (db.yourCollection.find(query).count() > 0) {
    db.yourCollection.update(
        query,
        { $set: { "events.$.handled": 0 } },
        { multi: true }
    );
}

ループが実行される回数は、コレクション内のいずれかのドキュメントでprofile10 にhandled等しく 0 に等しくないサブドキュメントが発生する最大回数に等しくなります。したがって、コレクションに 100 個のドキュメントがあり、そのうちの 1 つに一致するサブドキュメントが 3 つありquery、他のすべてのドキュメントには一致するサブドキュメントが少ない場合、ループは 3 回実行されます。

この方法により、このスクリプトの実行中に別のプロセスによって更新される可能性のある他のデータが破壊される危険が回避されます。また、クライアントとサーバー間で転送されるデータの量を最小限に抑えます。

于 2016-01-19T21:38:34.213 に答える
13

実際、これはhttp://jira.mongodb.org/browse/SERVER-1243の長年の問題に関連しています。実際には、複数の配列一致が存在する「すべてのケース」をサポートする明確な構文に対する多くの課題があります。見つかった。実際、この最初の投稿の後に実装された一括操作など、この問題の解決に「役立つ」方法が既に用意されています。

単一の更新ステートメントで複数の一致する配列要素を更新することはまだ不可能であるため、「複数の」更新を使用しても、更新できるのは、その単一のドキュメントごとに配列内の 1 つの数学要素だけです。声明。

現時点で考えられる最善の解決策は、一致するすべてのドキュメントを見つけてループし、一括更新を処理することです。これにより、少なくとも多くの操作を単一の応答で単一の要求で送信できます。.aggregate()必要に応じて、検索結果で返される配列の内容を更新選択の条件に一致するものだけに減らすために使用できます。

db.collection.aggregate([
    { "$match": { "events.handled": 1 } },
    { "$project": {
        "events": {
            "$setDifference": [
               { "$map": {
                   "input": "$events",
                   "as": "event",
                   "in": {
                       "$cond": [
                           { "$eq": [ "$$event.handled", 1 ] },
                           "$$el",
                           false
                       ]
                   }
               }},
               [false]
            ]
        }
    }}
]).forEach(function(doc) {
    doc.events.forEach(function(event) {
        bulk.find({ "_id": doc._id, "events.handled": 1  }).updateOne({
            "$set": { "events.$.handled": 0 }
        });
        count++;

        if ( count % 1000 == 0 ) {
            bulk.execute();
            bulk = db.collection.initializeOrderedBulkOp();
        }
    });
});

if ( count % 1000 != 0 )
    bulk.execute();

その.aggregate()部分は、配列に「一意の」識別子がある場合、または各要素のすべてのコンテンツが「一意の」要素自体を形成する場合に機能します。これは、一致する配列を処理するために使用される操作から返された値$setDifferenceをフィルタリングするために使用される "set" 演算子によるものです。false$map

配列のコンテンツに一意の要素がない場合は、次の代替アプローチを試すことができます$redact

db.collection.aggregate([
    { "$match": { "events.handled": 1 } },
    { "$redact": {
        "$cond": {
            "if": {
                "$eq": [ { "$ifNull": [ "$handled", 1 ] }, 1 ]
            },
            "then": "$$DESCEND",
            "else": "$$PRUNE"
        }
    }}
])

制限があるのは、「処理された」が実際に他のドキュメント レベルに存在することを意図したフィールドである場合、予期しない結果が得られる可能性がありますが、そのフィールドが 1 つのドキュメント位置にのみ表示され、等値一致である場合は問題ありません。

執筆時点での将来のリリース (3.1 以降の MongoDB ) では、$filterより簡単な操作が行われます。

db.collection.aggregate([
    { "$match": { "events.handled": 1 } },
    { "$project": {
        "events": {
            "$filter": {
                "input": "$events",
                "as": "event",
                "cond": { "$eq": [ "$$event.handled", 1 ] }
            }
        }
    }}
])

また、サポートするすべてのリリースで.aggregate()は、次のアプローチを$unwindで使用できますが、その演算子を使用すると、パイプラインで配列が拡張されるため、最も効率の悪いアプローチになります。

db.collection.aggregate([
    { "$match": { "events.handled": 1 } },
    { "$unwind": "$events" },
    { "$match": { "events.handled": 1 } },
    { "$group": {
        "_id": "$_id",
        "events": { "$push": "$events" }
    }}        
])

MongoDB バージョンが集約出力からの「カーソル」をサポートするすべての場合において、これはアプローチを選択し、一括更新ステートメントを処理するために示されているのと同じコード ブロックで結果を反復するだけの問題です。集約出力からの一括操作と「カーソル」は同じバージョン (MongoDB 2.6) で導入されているため、通常は処理のために連携して動作します。

以前のバージョンでも.find()、カーソルを返すために使用し、ステートメントの実行を除外して、.update()反復で配列要素が一致した回数だけにするのがおそらく最善です。

db.collection.find({ "events.handled": 1 }).forEach(function(doc){ 
    doc.events.filter(function(event){ return event.handled == 1 }).forEach(function(event){
        db.collection.update({ "_id": doc._id },{ "$set": { "events.$.handled": 0 }});
    });
});

「マルチ」更新を実行することを絶対に決定している場合、または一致したドキュメントごとに複数の更新を処理するよりも最終的に効率的であると判断した場合は、可能な配列一致の最大数をいつでも決定し、その数だけ「マルチ」更新を実行できます。基本的に更新するドキュメントがなくなるまで。

MongoDB 2.4 および 2.2 バージョンの有効なアプローチを使用.aggregate()して、この値を見つけることもできます。

var result = db.collection.aggregate([
    { "$match": { "events.handled": 1 } },
    { "$unwind": "$events" },
    { "$match": { "events.handled": 1 } },
    { "$group": {
        "_id": "$_id",
        "count": { "$sum": 1 }
    }},
    { "$group": {
        "_id": null,
        "count": { "$max": "$count" }
    }}
]);

var max = result.result[0].count;

while ( max-- ) {
    db.collection.update({ "events.handled": 1},{ "$set": { "events.$.handled": 0 }},{ "multi": true })
}

いずれにせよ、更新中にやりたくないことがいくつかあります。

  1. 配列を「ワンショット」で更新しない$setでください。配列の内容全体をコードで更新してから、各ドキュメントの配列全体だけを更新する方が効率的であると思われる場合。これは処理が速いように見えるかもしれませんが、配列の内容が読み取られて更新が実行されてから変更されていないという保証はありません。は依然としてアトミック$setオペレータですが、正しいデータであると「考える」ものでのみ配列を更新するため、読み取りと書き込みの間に発生した変更を上書きする可能性があります。

  2. 更新するインデックス値を計算しない:「ワンショット」アプローチと同様に、その位置0と位置2(など) が更新する要素であり、これらを次のような最終的なステートメントでコード化します。

    { "$set": {
        "events.0.handled": 0,
        "events.2.handled": 0
    }}
    

    繰り返しになりますが、ここでの問題は、ドキュメントが読み取られたときに見つかったインデックス値が、更新時の配列内の同じインデックス値であるという「推定」です。順序を変更する方法で新しい項目が配列に追加されると、それらの位置は無効になり、実際には間違った項目が更新されます。

したがって、複数の一致する配列要素を単一の更新ステートメントで処理できるようにするための合理的な構文が決定されるまで、基本的なアプローチは、一致する各配列要素を個々のステートメントで (理想的には Bulk で) 更新するか、基本的に配列要素の最大数を計算することです。更新するか、変更された結果が返されなくなるまで更新を続けます。いずれにせよ、ステートメントごとに 1 つの要素のみを更新する場合でも、一致した配列要素の位置$更新を「常に」処理する必要があります。

バルク操作は、実際には、「複数の操作」になる操作を処理するための「一般化された」ソリューションであり、複数の配列要素を同じ値で更新するだけでなく、これには多くのアプリケーションがあるため、もちろん実装されています。すでに、この問題を解決するための最良のアプローチです。

于 2015-10-18T00:58:31.940 に答える
11

MongoDB のすべての要素を更新できます

db.collectioname.updateOne(
{ "key": /vikas/i },
{ $set: { 
 "arr.$[].status" : "completed"
} }
)

「arr」配列のすべての「status」値を「completed」に更新します

文書が 1 つだけの場合

db.collectioname.updateOne(
 { key:"someunique", "arr.key": "myuniq" },
 { $set: { 
   "arr.$.status" : "completed", 
   "arr.$.msgs":  {
                "result" : ""
        }
   
 } }
)

ただし、1 つではなく、配列内のすべてのドキュメントを更新したくない場合は、要素と if ブロック内をループする必要があります

db.collectioname.find({findCriteria })
  .forEach(function (doc) {
    doc.arr.forEach(function (singlearr) {
      if (singlearr check) {
        singlearr.handled =0
      }
    });
    db.collection.save(doc);
  });
于 2020-06-22T10:25:03.587 に答える
8

これがまだmongoで対処されていないことに驚いています。サブアレイを扱う場合、全体的なmongoはあまり良くないようです。たとえば、単純にサブ配列を数えることはできません。

ハビエルの最初のソリューションを使用しました。配列をイベントに読み込み、ループしてセット exp を構築します。

var set = {}, i, l;
for(i=0,l=events.length;i<l;i++) {
  if(events[i].profile == 10) {
    set['events.' + i + '.handled'] = 0;
  }
}

.update(objId, {$set:set});

これは、条件テストのコールバックを使用して関数に抽象化できます

于 2013-06-02T13:54:17.323 に答える
2

$[] の使用を示唆するこのスレッドのいくつかの回答は間違っていることに注意してください。

db.collection.update(
   {"events.profile":10},
   {$set:{"events.$[].handled":0}},
   {multi:true}
)

上記のコードは、"profile" 値に関係なく、"events" 配列内のすべての要素の "handled" を 0 に更新します。クエリ{"events.profile":10}は、配列内のドキュメントではなく、ドキュメント全体をフィルタリングすることのみを目的としています。この状況では、配列項目の条件を指定するため$[elem]に withを使用する必要があるarrayFiltersため、Neil Lunn の答えは正しいです。

于 2019-08-19T02:22:05.360 に答える
0

$[] 演算子は、ネストされたすべての配列を選択します..すべての配列項目を '$[]' で更新できます

.update({"events.profile":10},{$set:{"events.$[].handled":0}},false,true)

参照

于 2019-08-12T11:24:46.557 に答える