你实际上选择了"文档",这是查询应该做的。但你要找的是"过滤包含的数组",以使返回的元素只匹配查询条件。
答案当然是,除非通过过滤掉这样的细节可以节省大量带宽,否则你甚至不应该尝试,或者至少超出第一个位置匹配。
MongoDB有一个位置$
运算符,将返回与查询条件匹配的索引处的数组元素。然而,这仅返回"外部"最多数组元素的"第一个"匹配索引。
db.getCollection('retailers').find(
{ 'stores.offers.size': 'L'},
{ 'stores.$': 1 }
)
在这种情况下,它只意味着"stores"
数组位置。因此,如果有多个"stores"条目,则只会返回包含匹配条件的元素中的"一个"。但是,这对于"offers"
内部数组没有任何作用,因此在匹配的"stores"
数组中的每个"offer"仍将被返回。
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"]
的数组("set")与目标数组进行比较。当条件为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);
})
使用返回的"post"对象进行查询处理远比使用聚合管道来完成更加简单明了。如前所述,唯一的“真正”差异在于当接收到数据时,您放弃了“服务器”上的其他元素,而不是“按文档”删除它们,这可能会节省一些带宽。
但是,除非您在仅具有$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})
。 但是响应结果也包含了错误的优惠。 - Vico$match
和$unwind
聚合吗? - Vico