因此,您实际上已经在查询中选择了“文档”。但是,您要查找的是“过滤包含的数组”,以便返回的元素仅与查询条件匹配。
真正的答案当然是,除非您通过过滤掉这些细节确实节省了很多带宽,否则您甚至不应该尝试,或者至少不要超过第一个位置匹配。
MongoDB有一个位置$
运算符,它将根据查询条件返回匹配索引处的数组元素。但是,这仅返回“最外面”的数组元素的“第一个”匹配索引。
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
这是要走的路,因为它的设计考虑了目标。由于阵列有多个级别,因此您需要在每个级别上应用它。所以,首先你是在深入研究"offers"
中"stores"
,以examime和$filter
该内容。
这里的简单比较是“"size"
数组是否包含我要查找的元素”。在此逻辑上下文中,要做的简短事情是使用该$setIsSubset
操作将一个数组(“集合”)["L"]
与目标数组进行比较。如果该条件为true
(它包含“ L”),则将"offers"
保留的数组元素并在结果中返回。
在较高的水平$filter
,你再看看是否从以前的结果$filter
返回一个空数组[]
的"offers"
。如果不为空,则返回该元素,否则将其删除。
MongoDB 2.6.x
这与现代流程非常相似,不同之处在于,由于$filter
此版本中没有,您可以$map
用来检查每个元素,然后使用$setDifference
来过滤出以返回的任何元素false
。
因此$map
将要返回整个数组,但是该$cond
操作仅决定是返回元素还是返回false
值。与$setDifference
单个元素“ set”进行比较时,返回数组中的[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) {
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);
})
因此,与使用聚合管道执行此操作相比,使用返回的对象“后”查询处理要轻松得多。如前所述,唯一的“真实”差异是您要丢弃“服务器”上的其他元素,而不是在接收到“每个文档”时将其删除,这样可以节省一点带宽。
但是,除非您在仅包含 $match
和的现代版本中执行此操作,否则$project
服务器上处理的“成本”将大大超过通过首先剥离不匹配的元素来减少网络开销的“收益”。
在所有情况下,您都会得到相同的结果:
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"stores" : [
{
"_id" : ObjectId("56f277b5279871c20b8b4783"),
"offers" : [
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"size" : [
"S",
"L",
"XL"
]
}
]
}
]
}
db.getCollection('retailers').find({'stores.offers.size': 'L'}, {'stores.offers': 1})
。但随后,响应中也包含错误的报价