目前的问题
您应该从previously linked question 中了解到positional $ operator 本身只能匹配数组中的第一个匹配元素。当您像您一样拥有嵌套数组时,这意味着“始终”只能报告“外部”数组,而永远不会报告内部数组中的实际匹配位置,也不会超过单个匹配。
其他示例显示了 MongoDB 聚合框架的用法,以便通过通常使用 $unwind 进行处理,然后使用条件匹配您需要的数组元素来“过滤”数组中的元素。在这种情况下,这通常是您需要执行的操作,以便从“内部”数组中获取匹配项。虽然自第一个答案以来已经有所改进,但您的“最后一场比赛”或实际上是“切片”条件,排除了其他目前的可能性。因此:
db.junk.aggregate([
{ "$match": {
"designs.details.status": "OLD"
}},
{ "$unwind": "$designs" },
{ "$unwind": "$designs.details" },
{ "$group": {
"_id": {
"_id": "$_id",
"Deid": "$designs.Deid"
},
"details": { "$last": "$designs.details"}
}},
{ "$match": {
"details.status": "OLD"
}},
{ "$group": {
"_id": "$_id",
"details": { "$push": "$details"}
}},
{ "$group": {
"_id": "$_id._id",
"designs": {
"$push": {
"Deid": "$_id.Deid",
"details": "$details"
}
}
}}
])
这会在您的文档或任何其他类似的文档上返回如下结果:
{
"_id" : ObjectId("55cddc30f1a3c59ca1e88f30"),
"designs" : [
{
"Deid" : 2,
"details" : [
{
"SLID" : 3,
"status" : "OLD"
}
]
}
]
}
关键在于$unwind 两个数组,然后$group 返回相关的唯一元素,以便从每个“内部”数组中“切片”$last 元素。
您的下一个条件需要$match 才能看到该元素的“状态”字段是您想要的值。当然,由于文档基本上已经被 $unwind 操作“去规范化”,即使是随后的 $group,下面的 $group 语句将文档重新构造为原始形式。
聚合管道可能非常简单,也可能非常困难,具体取决于您想要做什么,并且像这样使用过滤重建文档意味着您需要注意这些步骤,尤其是在涉及其他字段的情况下。正如您在这里也应该意识到的,$unwind 去规范化和$group 操作的过程不是很有效,并且可能会导致大量开销,具体取决于初始$match 查询可以满足的可能文档的数量.
更好的解决方案
虽然目前仅在当前的开发分支中可用,但聚合管道中有一些新的运算符可用,这使得聚合管道更加高效,并且有效地与一般查询的性能“相提并论”。这些特别是 $filter 和 $slice 运算符,在这种情况下可以使用如下:
db.junk.aggregate([
{ "$match": {
"designs.details.status": "OLD"
}},
{ "$redact": {
"$cond": [
{ "$gt": [
{ "$size":{
"$filter": {
"input": "$designs",
"as": "designs",
"cond": {
"$anyElementTrue":[
{ "$map": {
"input": {
"$slice": [
"$$designs.details",
-1
]
},
"as": "details",
"in": {
"$eq": [ "$$details.status", "OLD" ]
}
}}
]
}
}
}},
0
]},
"$$KEEP",
"$$PRUNE"
]
}},
{ "$project": {
"designs": {
"$map": {
"input": {
"$filter": {
"input": "$designs",
"as": "designs",
"cond": {
"$anyElementTrue":[
{ "$map": {
"input": {
"$slice": [
"$$designs.details",
-1
]
},
"as": "details",
"in": {
"$eq": [ "$$details.status", "OLD" ]
}
}}
]
}
}
},
"as": "designs",
"in": {
"Deid": "$$designs.Deid",
"details": { "$slice": [ "$$designs.details", -1] }
}
}
}
}}
])
这有效地使操作仅成为$match 和$project 阶段,这基本上是一般.find() 操作所做的。这里唯一真正添加的是$redact 阶段,它允许通过可以检查文档的进一步逻辑条件从初始查询条件中额外过滤文档。
在这种情况下,我们可以查看文档是否不仅包含“OLD”状态,而且这是至少一个内部数组的最后一个元素与它自己的最后一个条目的状态匹配,否则它是从不满足该条件的结果中“删减”。
在$redact 和$project 中,$slice 运算符用于从“designs”数组中的“details”数组中获取最后一个条目。在初始情况下,它与$filter 一起应用以从“外部”或“设计”数组中删除条件不匹配的任何元素,然后在$project 中仅显示“设计”中的最后一个元素" 最终演示文稿中的数组。最后一次“重塑”由$map 完成,仅用最后一个元素切片替换整个数组。
虽然其中的逻辑似乎比最初的陈述要冗长得多,但性能提升可能是“巨大的”,因为能够将每个文档视为一个“单元”而无需反规范化或以其他方式解构,直到进行最终投影。
目前的最佳解决方案
总而言之,您可以用来实现结果的当前流程根本无法有效解决问题。实际上,简单地将满足基本条件(包含“状态”,即“旧”)的文档与$where 条件相结合来测试每个数组的最后一个元素会更有效。然而,输出中数组的实际“过滤”最好留给客户端代码:
db.junk.find({
"designs.details.status": "OLD",
"$where": function() {
return this.designs.some(function(design){
return design.details.slice(-1)[0].status == "OLD";
});
}
}).forEach(function(doc){
doc.designs = doc.designs.filter(function(design) {
return design.details.slice(-1)[0].status == "OLD";
}).map(function(design) {
design.details = design.details.slice(-1);
return design;
});
printjson(doc);
});
所以查询条件至少只返回匹配所有条件的文档,然后客户端代码通过测试最后一个元素从数组中过滤出内容,然后将最后一个元素切出作为要显示的内容。
目前,这可能是最有效的方法,因为它反映了未来的聚合操作能力。
问题实际上出在数据结构上。虽然它可能适合您的应用程序的显示目的,但由于前面提到的位置运算符的限制,嵌套数组的使用使查询变得非常困难,并且“不可能”进行原子更新。
虽然您始终可以通过匹配内部数组的存在或仅存在外部数组元素来将新元素$push 匹配到内部数组,但您不能做的是在原子操作中更改内部元素的“状态”。为了这样修改,你需要检索整个文档,然后在代码中修改内容,并将结果保存回来。
该过程的问题意味着您可能会在更新上“冲突”,并可能覆盖另一个并发请求对您当前正在处理的请求所做的更改。
出于这些原因,您确实应该重新考虑应用程序的所有设计目标以及对这种结构的适用性。保持更非规范化的形式可能会在其他一些方面花费您,但它肯定会使查询和更新变得更加简单,并使用这似乎需要的那种检查级别。
这里的最终结论应该是您重新考虑设计。尽管现在和将来都有可能获得结果,但其他操作障碍应该足以保证进行更改。