Vic*_*ico 58 mongodb mongodb-query aggregation-framework
主要集合是零售商,其中包含商店阵列.每个商店都包含一系列优惠(您可以在这家商店购买).这个提供的数组有一系列的大小.(见下面的例子)
现在我尝试找到所有尺寸可用的优惠L.
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"stores" : [
{
"_id" : ObjectId("56f277b5279871c20b8b4783"),
"offers" : [
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"size": [
"XS",
"S",
"M"
]
},
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"size": [
"S",
"L",
"XL"
]
}
]
}
}
Run Code Online (Sandbox Code Playgroud)
我试过这个查询: db.getCollection('retailers').find({'stores.offers.size': 'L'})
我期待一些像这样的输出:
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"stores" : [
{
"_id" : ObjectId("56f277b5279871c20b8b4783"),
"offers" : [
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"size": [
"S",
"L",
"XL"
]
}
]
}
}
Run Code Online (Sandbox Code Playgroud)
但我的查询输出还包含与sizeXS,X和M 的不匹配的报价.
我如何强制MongoDB只返回与我的查询匹配的商品?
问候和感谢.
Bla*_*ven 108
因此,您实际上选择"文档"的查询就像它应该的那样.但是你要找的是"过滤所包含的数组",这样返回的元素只匹配查询的条件.
真正的答案当然是除非你真的通过过滤掉这些细节来节省大量带宽,否则你甚至不应该尝试,或者至少超过第一次位置匹配.
MongoDB有一个位置$运算符,它将从查询条件返回匹配索引处的数组元素.但是,这只返回"外部"最多数组元素的"第一个"匹配索引.
db.getCollection('retailers').find(
{ 'stores.offers.size': 'L'},
{ 'stores.$': 1 }
)
Run Code Online (Sandbox Code Playgroud)
在这种情况下,它"stores"仅表示数组位置.因此,如果有多个"store"条目,则只返回包含匹配条件的"one"元素.但是,这对内部数组没有任何作用"offers",因此匹配"stores"数组中的每个"提供" 仍将被返回.
MongoDB无法在标准查询中"过滤"它,因此以下方法不起作用:
db.getCollection('retailers').find(
{ 'stores.offers.size': 'L'},
{ 'stores.$.offers.$': 1 }
)
Run Code Online (Sandbox Code Playgroud)
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", [] ]}
}
}
}}
])
Run Code Online (Sandbox Code Playgroud)
然后用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]
]
}
}}
])
Run Code Online (Sandbox Code Playgroud)
最后在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"
}
}
}}
])
Run Code Online (Sandbox Code Playgroud)
让我们分解一下解释.
所以一般来说,$filter这是走向这里的方式,因为它的设计目的在于此.由于阵列有多个级别,因此您需要在每个级别应用此级别.所以,首先你是在深入研究"offers"中"stores",以examime和$filter该内容.
这里简单的比较是" "size"数组是否包含我正在寻找的元素".在这个逻辑上下文中,要做的就是使用该$setIsSubset操作来比较["L"]目标数组的数组("set").在那个条件是true(它包含"L")的情况下,数组元素将"offers"被保留并返回到结果中.
在较高的水平$filter,你再看看是否从以前的结果$filter返回一个空数组[]的"offers".如果它不为空,则返回元素或以其他方式删除元素.
这与现代流程非常相似,不同之处在于,由于$filter此版本中没有,您可以使用它$map来检查每个元素,然后用于$setDifference过滤掉所返回的任何元素false.
所以$map将返回整个数组,但$cond操作只是决定是返回元素还是返回false值.在与$setDifference单个元素的比较中,将删除返回数组中[false]所有false元素的"set" .
在所有其他方面,逻辑与上面相同.
所以下面的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);
})
Run Code Online (Sandbox Code Playgroud)
因此,使用返回的对象"post"查询处理远比使用聚合管道执行此操作要迟钝.如上所述,唯一的"真正"差异在于您丢弃"服务器"上的其他元素,而不是在接收时"按文档"删除它们,这可能会节省一点带宽.
但是,除非你用这样的现代版本只 $match和$project,然后在服务器上处理的"成本"将大大超过了由第一剥离匹配元素减少了网络开销的"增益".
在所有情况下,您都会得到相同的结果:
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"stores" : [
{
"_id" : ObjectId("56f277b5279871c20b8b4783"),
"offers" : [
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"size" : [
"S",
"L",
"XL"
]
}
]
}
]
}
Run Code Online (Sandbox Code Playgroud)
pro*_*r79 10
当你的数组被嵌入时我们不能使用$ elemMatch,而是你可以使用聚合框架来获得你的结果:
db.retailers.aggregate([
{$match:{"stores.offers.size": 'L'}}, //just precondition can be skipped
{$unwind:"$stores"},
{$unwind:"$stores.offers"},
{$match:{"stores.offers.size": 'L'}},
{$group:{
_id:{id:"$_id", "storesId":"$stores._id"},
"offers":{$push:"$stores.offers"}
}},
{$group:{
_id:"$_id.id",
stores:{$push:{_id:"$_id.storesId","offers":"$offers"}}
}}
]).pretty()
Run Code Online (Sandbox Code Playgroud)
这个查询的作用是展开数组(两次),然后匹配大小,然后将文档重新整形为前一个表单.您可以删除$ group步骤并查看其打印方式.玩得开心!
| 归档时间: |
|
| 查看次数: |
59973 次 |
| 最近记录: |