实际上,这确实与http://jira.mongodb.org/browse/SERVER-1243上的长期存在的问题有关,实际上,支持多数组匹配的“所有情况”的清晰语法存在许多挑战。找到了。实际上,已经有解决此问题的“方法”,例如在本原始帖子之后实施的批量操作。
仍然不可能在单个update语句中更新多个匹配的数组元素,因此,即使使用“多”更新,您也只能在数组中为单个文档中的每个文档更新一个数学元素声明。
当前最好的解决方案是查找并循环所有匹配的文档并处理批量更新,这至少将允许在单个请求中以单个响应发送许多操作。您可以选择使用.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()
当数组具有“唯一”标识符或每个元素的所有内容形成“唯一”元素本身时,将在该部分工作。这是由于使用了“ set”运算符$setDifference
来过滤false
从$map
用于处理数组以进行匹配的操作返回的任何值。
如果您的数组内容没有唯一元素,则可以尝试以下替代方法$redact
:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$redact": {
"$cond": {
"if": {
"$eq": [ { "$ifNull": [ "$handled", 1 ] }, 1 ]
},
"then": "$$DESCEND",
"else": "$$PRUNE"
}
}}
])
局限性在于,如果“处理”实际上是要在其他文档级别存在的字段,那么您可能会得到意料之外的结果,但是如果该字段仅出现在一个文档位置并且是相等匹配项,那就很好了。
在撰写本文时,未来的发行版(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
}}
同样,这里的问题是“推定”,即在读取文档时发现的那些索引值与更新时数组中的索引值相同。如果以更改顺序的方式将新项目添加到数组,则这些位置不再有效,并且实际上更新了错误的项目。
因此,在确定合理的语法以允许在单个update语句中处理多个匹配的数组元素之前,基本方法是要么更新单个语句中的每个匹配的数组元素(理想情况下为Bulk),要么实质上算出最大的数组元素更新或保持更新,直到没有更多修改结果返回为止。无论如何,您应该“始终” 对匹配的数组元素进行位置$
更新,即使每个语句仅更新一个元素也是如此。
批量操作实际上是处理任何被计算为“多个操作”的操作的“通用”解决方案,并且由于有更多的应用程序,而不是仅更新具有相同值的多个数组元素,因此当然已经实现了已经是目前解决该问题的最佳方法。