実際、これは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 })
}
いずれにせよ、更新中にやりたくないことがいくつかあります。
配列を「ワンショット」で更新しない$set
でください。配列の内容全体をコードで更新してから、各ドキュメントの配列全体だけを更新する方が効率的であると思われる場合。これは処理が速いように見えるかもしれませんが、配列の内容が読み取られて更新が実行されてから変更されていないという保証はありません。は依然としてアトミック$set
オペレータですが、正しいデータであると「考える」ものでのみ配列を更新するため、読み取りと書き込みの間に発生した変更を上書きする可能性があります。
更新するインデックス値を計算しない:「ワンショット」アプローチと同様に、その位置0
と位置2
(など) が更新する要素であり、これらを次のような最終的なステートメントでコード化します。
{ "$set": {
"events.0.handled": 0,
"events.2.handled": 0
}}
繰り返しになりますが、ここでの問題は、ドキュメントが読み取られたときに見つかったインデックス値が、更新時の配列内の同じインデックス値であるという「推定」です。順序を変更する方法で新しい項目が配列に追加されると、それらの位置は無効になり、実際には間違った項目が更新されます。
したがって、複数の一致する配列要素を単一の更新ステートメントで処理できるようにするための合理的な構文が決定されるまで、基本的なアプローチは、一致する各配列要素を個々のステートメントで (理想的には Bulk で) 更新するか、基本的に配列要素の最大数を計算することです。更新するか、変更された結果が返されなくなるまで更新を続けます。いずれにせよ、ステートメントごとに 1 つの要素のみを更新する場合でも、一致した配列要素の位置$
更新を「常に」処理する必要があります。
バルク操作は、実際には、「複数の操作」になる操作を処理するための「一般化された」ソリューションであり、複数の配列要素を同じ値で更新するだけでなく、これには多くのアプリケーションがあるため、もちろん実装されています。すでに、この問題を解決するための最良のアプローチです。