因此,您的查询实际上选择了“文档”,就像它应该的那样。但是您正在寻找的是“过滤包含的数组”,以便返回的元素仅匹配查询的条件。
真正的答案当然是,除非你真的通过过滤掉这些细节来节省大量带宽,否则你甚至不应该尝试,或者至少在第一次位置匹配之后。
MongoDB 有一个positional $ operator,它将在查询条件的匹配索引处返回一个数组元素。但是,这只会返回“最外层”数组元素的“第一个”匹配索引。
db.getCollection('retailers').find(
{ 'stores.offers.size': 'L'},
{ 'stores.$': 1 }
)
在这种情况下,它仅表示"stores" 数组位置。因此,如果有多个“商店”条目,则只会返回包含匹配条件的“一个”元素。 但是,这对"offers" 的内部数组没有任何作用,因此匹配的"stores" 数组中的每个“报价”仍然会被返回。
MongoDB 无法在标准查询中“过滤”此内容,因此以下内容不起作用:
db.getCollection('retailers').find(
{ 'stores.offers.size': 'L'},
{ 'stores.$.offers.$': 1 }
)
MongoDB 真正必须执行此级别操作的唯一工具是聚合框架。但是分析应该告诉你为什么你“可能”不应该这样做,而只是在代码中过滤数组。
按照每个版本如何实现这一点的顺序。
首先使用 MongoDB 3.2.x 并使用 $filter 操作:
db.getCollection('retailers').aggregate([
{ "$match": { "stores.offers.size": "L" } },
{ "$project": {
"stores": {
"$filter": {
"input": {
"$map": {
"input": "$stores",
"as": "store",
"in": {
"_id": "$$store._id",
"offers": {
"$filter": {
"input": "$$store.offers",
"as": "offer",
"cond": {
"$setIsSubset": [ ["L"], "$$offer.size" ]
}
}
}
}
}
},
"as": "store",
"cond": { "$ne": [ "$$store.offers", [] ]}
}
}
}}
])
然后使用 MongoDB 2.6.x 及更高版本使用 $map 和 $setDifference:
db.getCollection('retailers').aggregate([
{ "$match": { "stores.offers.size": "L" } },
{ "$project": {
"stores": {
"$setDifference": [
{ "$map": {
"input": {
"$map": {
"input": "$stores",
"as": "store",
"in": {
"_id": "$$store._id",
"offers": {
"$setDifference": [
{ "$map": {
"input": "$$store.offers",
"as": "offer",
"in": {
"$cond": {
"if": { "$setIsSubset": [ ["L"], "$$offer.size" ] },
"then": "$$offer",
"else": false
}
}
}},
[false]
]
}
}
}
},
"as": "store",
"in": {
"$cond": {
"if": { "$ne": [ "$$store.offers", [] ] },
"then": "$$store",
"else": false
}
}
}},
[false]
]
}
}}
])
最后在 MongoDB 2.2.x 以上的任何版本中引入了聚合框架。
db.getCollection('retailers').aggregate([
{ "$match": { "stores.offers.size": "L" } },
{ "$unwind": "$stores" },
{ "$unwind": "$stores.offers" },
{ "$match": { "stores.offers.size": "L" } },
{ "$group": {
"_id": {
"_id": "$_id",
"storeId": "$stores._id",
},
"offers": { "$push": "$stores.offers" }
}},
{ "$group": {
"_id": "$_id._id",
"stores": {
"$push": {
"_id": "$_id.storeId",
"offers": "$offers"
}
}
}}
])
让我们分解解释。
MongoDB 3.2.x 及更高版本
所以一般来说,$filter 是这里的方式,因为它的设计考虑了目的。由于数组有多个级别,因此您需要在每个级别上应用它。因此,首先您要深入了解"stores" 中的每个"offers" 以检查和$filter 该内容。
这里的简单比较是“"size" 数组中是否包含我要查找的元素”。在这个逻辑上下文中,要做的简短的事情是使用$setIsSubset 操作将["L"] 的数组(“集合”)与目标数组进行比较。如果条件是 true(它包含 "L" ),则 "offers" 的数组元素将被保留并在结果中返回。
在更高级别的$filter 中,您将查看前一个$filter 的结果是否为"offers" 返回了一个空数组[]。如果不为空,则返回该元素,否则将其删除。
MongoDB 2.6.x
这与现代流程非常相似,只是由于此版本中没有$filter,因此您可以使用$map 检查每个元素,然后使用$setDifference 过滤掉返回为@987654357 的任何元素@。
所以$map 将返回整个数组,但$cond 操作只是决定是返回元素还是false 值。在将$setDifference 与[false] 的单个元素“集合”进行比较时,返回数组中的所有false 元素都将被删除。
其他方面,逻辑同上。
MongoDB 2.2.x 及更高版本
因此,在 MongoDB 2.6 之下,处理数组的唯一工具是 $unwind,仅出于此目的,您应该不“仅”为此目的使用聚合框架。
这个过程确实看起来很简单,只需“拆开”每个阵列,过滤掉不需要的东西,然后将它们重新组合在一起。主要关心的是在“两个”$group 阶段,“第一个”重新构建内部数组,下一个重新构建外部数组。在所有级别都有不同的_id 值,因此只需将这些值包含在每个分组级别。
但问题是$unwind 成本非常高。虽然它仍然有目的,但它的主要使用意图不是对每个文档进行这种过滤。事实上,在现代版本中,它的唯一用途应该是当数组的元素需要成为“分组键”本身的一部分时。
结论
因此,像这样在数组的多个级别上获取匹配并不是一个简单的过程,事实上,如果实施不正确,可能会极其昂贵。
只有两个现代列表应该用于此目的,因为除了“查询”$match 之外,它们还使用“单个”管道阶段来执行“过滤”。由此产生的效果比.find() 的标准形式的开销略多。
不过,总的来说,这些列表对它们来说仍然具有一定的复杂性,而且确实,除非您真的以显着改善服务器和客户端之间使用的带宽的方式大幅减少此类过滤返回的内容,否则您最好过滤初始查询和基本投影的结果。
db.getCollection('retailers').find(
{ 'stores.offers.size': 'L'},
{ 'stores.$': 1 }
).forEach(function(doc) {
// Technically this is only "one" store. So omit the projection
// if you wanted more than "one" match
doc.stores = doc.stores.filter(function(store) {
store.offers = store.offers.filter(function(offer) {
return offer.size.indexOf("L") != -1;
});
return store.offers.length != 0;
});
printjson(doc);
})
因此,使用返回的对象“post”查询处理远没有使用聚合管道执行此操作那么迟钝。如前所述,唯一的“真正”区别是您将丢弃“服务器”上的其他元素,而不是在收到时“按文档”删除它们,这可能会节省一点带宽。
但除非您在仅 $match 和$project 的现代版本中执行此操作,否则服务器上处理的“成本”将大大超过减少它的“收益”通过首先剥离不匹配的元素来增加网络开销。
在所有情况下,您都会得到相同的结果:
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"stores" : [
{
"_id" : ObjectId("56f277b5279871c20b8b4783"),
"offers" : [
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"size" : [
"S",
"L",
"XL"
]
}
]
}
]
}