事实上,这与
http://jira.mongodb.org/browse/SERVER-1243
事实上,对于一个支持“所有情况”的清晰语法来说,如果发现多个数组匹配,则存在许多挑战。事实上,已经有一些方法可以“帮助”解决这个问题,比如
Bulk Operations
在这篇文章发表后已经实施了。
在一个update语句中仍然不可能更新多个匹配的数组元素,因此即使使用“multi”update,也只能更新该语句中每个文档的数组中的一个mathed元素。
目前最好的解决方案是找到并循环所有匹配的文档,并处理批量更新,这将至少允许在一个单一响应的请求中发送多个操作。您可以选择使用
.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
用于过滤任何
false
从返回的值
$map
用于处理数组中匹配项的操作。
如果数组内容没有唯一的元素,可以尝试使用
$redact
:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$redact": {
"$cond": {
"if": {
"$eq": [ { "$ifNull": [ "$handled", 1 ] }, 1 ]
},
"then": "$$DESCEND",
"else": "$$PRUNE"
}
}}
])
其中的限制是,如果“handled”实际上是一个字段,该字段应该出现在其他文档级别,那么您可能会得到未预期的结果,但如果该字段只出现在一个文档位置并且是相等匹配,则可以。
从编写之日起的未来版本(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
每个文档中的整个数组。处理起来可能更快,但不能保证阵列内容自读取并执行更新后没有更改。尽管
$集
仍然是一个原子运算符,它只会用它“认为”的正确数据更新数组,因此可能会覆盖读写之间发生的任何更改。
-
不计算要更新的索引值:
在类似于“一次射击”的方法中,你只需计算出那个位置
0
和位置
2
(依此类推)是用来更新和编码这些内容的元素,并最终声明如下:
{ "$set": {
"events.0.handled": 0,
"events.2.handled": 0
}}
这里的问题再次是“假定”文档读取时找到的索引值与更新时数组中的索引值相同。如果以更改顺序的方式将新项添加到数组中,则这些位置不再有效,错误项实际上会更新。
因此,直到有一个合理的语法被确定为允许在单个更新语句中处理多个匹配的数组元素,则基本方法是在一个动态语句中更新每一个匹配的数组元素(理想地是批量的),或者基本上计算出最大的数组元素以更新或保持更新,直到没有更多。返回修改后的结果。无论如何,你应该“一直”在处理
positional
$
对匹配的数组元素进行更新,即使每个语句只更新一个元素。
批量操作实际上是处理“多个操作”的任何操作的“广义”解决方案,而且由于有更多的应用程序可用于此,而不仅仅是更新具有相同值的多个数组元素,因此它当然已经实现,而且它目前是解决此问题的最佳方法。