[英]MongoDB: How to find (and return) elements in a multidimensional array?
[英]Mongodb: how to return elements of array that are present in the query list
我有一个叫做商店的收藏。 结构就像:
[
{
'_id' : id1,
'details' : {name: 'shopA'},
'products' : [{
_id: 'p1',
details: {
'name': 'product1'
}
},{
_id: 'p2',
details: {
'name': 'product2'
}
}, {
_id: 'p4',
details: {
'name': 'product4'
}
}
},{
'_id' : id2,
'details' : {name: 'shopB'},
'products' : [{
_id: 'p1',
details: {
'name': 'product1'
}
},{
_id: 'p4',
details: {
'name': 'product4'
}
}, {
_id: 'p5',
details: {
'name': 'product5'
}
}
},{
'_id' : id3,
'details' : {name: 'shopC'},
'products' : [{
_id: 'p1',
details: {
'name': 'product1'
}
},{
_id: 'p2',
details: {
'name': 'product2'
}
}, {
_id: 'p3',
details: {
'name': 'product3'
}
}
},{
'_id' : id4,
'details' : {name: 'shopOther'},
'products' : [{
_id: 'p10',
details: {
'name': 'product10'
}
},{
_id: 'p12',
details: {
'name': 'product12'
}
}, {
_id: 'p13',
details: {
'name': 'product13'
}
}
}
]
现在,用户可以从菜单中选择一些产品,然后尝试找到这些商店。 结果应该是所有提供至少一项选定商品的商店。
例,
假设用户选择['p1', 'p2', 'p3'] //ids
那么只会列出三个商店id1,id2,id3(id4没有这些项目),加上这样的结构可以删除其余的结果数组中来自文档的商店产品(未列出)。
有没有办法,我可以直接从mongodb获得这样的结果?
由于您确实提出了很好的要求,而且格式也很合理,因此需要考虑一些类似的答案可能实际上并不适合作为参考,特别是如果您对MongoDB产品的经验水平很低时。
$redact
类的选项似乎很简单,通常很适合。 但这不是您需要如何构造语句的情况:
db.collection.aggregate([
{ "$match": { "products._id": { "$in": ["p1","p2","p3"] } }},
{ "$redact": {
"$cond": {
"if": {
"$or": [
{ "$eq": [ "$_id", "p1" ] },
{ "$eq": [ "$_id", "p2" ] },
{ "$eq": [ "$_id", "p3" ] }
]
},
"then": "$$DESCEND",
"else": "$$PRUNE"
}
}}
])
这与$or
在聚合运算符中的“不太明显”用法一起使用。 至少就正确的语法和形式而言,但这实际上是“完全失败”。 原因是因为$redact
通常是“递归”操作,并且它在文档的“所有级别”进行检查,而不仅仅是在特定级别进行检查。 因此,在您的“顶层”中, _id
断言将失败,因为同名的顶层字段将不符合该条件。
实际上,您没有任何其他可以做的事情,但是考虑到数组中的_id
实际上是一个“唯一”元素,那么您始终可以在$project
阶段借助$map
和$setDifference
来执行此操作:
db.collection.aggregate([
{ "$match": { "products._id": { "$in": ["p1","p2","p3"] } }},
{ "$project": {
"details": 1,
"products": {
"$setDifference": [
{ "$map": {
"input": "$products",
"as": "el",
"in": {
"$cond": {
"if": {
"$or": [
{ "$eq": [ "$$el._id", "p1" ] },
{ "$eq": [ "$$el._id", "p2" ] },
{ "$eq": [ "$$el._id", "p3" ] }
]
},
"then": "$$el",
"else": false
}
}
}},
[false]
]
}
}}
])
似乎很长,但实际上非常有效。 $map
运算符为每个文档“内联”处理数组,并作用于每个元素以产生新的数组。 与$setDifference
相比,通过考虑结果的“集合”,可以平衡在条件不匹配的$cond
下作出的false
断言,后者可以有效地“过滤”结果数组中的false
结果,仅保留有效的匹配项。
当然,如果_id
值或整个对象不是真正的“唯一”,则“集合”将不再有效。 考虑到这一点,以及上述提到的运算符不可用于2.6之前的版本的事实,那么更传统的方法是$unwind
数组成员,然后通过$match
操作“过滤”它们。
db.collection.aggregate([
{ "$match": { "products._id": { "$in": ["p1","p2","p3"] } }},
{ "$unwind": "$products" },
{ "$match": { "products._id": { "$in": ["p1","p2","p3"] } }},
{ "$group": {
"_id": "$_id",
"details": { "$first": "$details" },
"products": { "$push": "$products" }
}}
])
考虑到按照其他示例, $match
阶段应首先在管道中执行,以减少匹配条件的“可能”文档。 具有$match
的“第二”阶段以“非规范化”形式对数组内的文档元素进行实际的“过滤”。
由于数组是由$unwind
“解构”的,因此$group
的目的是“重建”数组,并从与条件不匹配的元素中“过滤”出来。
MongoDB还提供了位置$
运算符,以便从查询条件中选择匹配的数组元素。 像这样:
db.collection.find(
{ "products._id": { "$in": ["p1","p2","p3"] },
{ "details": 1, "products.$": 1 }
)
但是这里的问题是,此运算符仅在查询文档中提供的条件上支持“第一”匹配。 这是设计目的,到目前为止,还没有严格的运算符语法可以满足多个匹配项。
因此,您的最终方法是当前使用.aggregate()
方法,以便在所需的内部数组上实际实现匹配过滤。 要么过滤内容,要么在客户端代码中响应自己,这取决于最终对您的可口性。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.