Mongodb: 如何 return 查询列表中存在的数组元素
Mongodb: how to return elements of array that are present in the query list
我有一个名为 shops 的集合。结构如下:
[
{
'_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'
}
}
}
]
现在用户可以 select 菜单中的一些产品并尝试为这些产品找到商店。结果应该是所有提供至少一种 selected 商品的商店。
示例,
假设用户 select ['p1', 'p2', 'p3'] //ids
然后只有三个店
id1、id2、id3 将被列出(id4 有 none 个这些项目),加上结构是这样的,它从结果数组的文档中删除商店的其余产品(未列出)。
有没有办法,我可以直接从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"
}
}}
])
这与在聚合运算符中 "not so obvious" 使用 $or
一起使用。至少在语法和形式上是正确的,但它实际上是一个"complete fail"。原因是因为 $redact
通常是一个 "recursive" 操作,它检查文档的 "all levels" 而不是特定级别。因此,您在 "top level" 中的 _id
断言将失败,因为同名的顶级字段将不匹配该条件。
你真的没有什么可以做的,但考虑到数组中的 _id
实际上是一个 "unique" 元素,那么你总是可以在 $project
stage with the help of $map
and $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
运算符为每个文档处理数组 "inline" 并作用于每个元素以生成一个新数组。 false
在 $cond
下做出的条件不匹配的断言通过考虑与 $setDifference
相比的 "set" 结果来平衡,这实际上 "filters" false
结果数组的结果,只留下有效的匹配项。
当然,如果 _id
值或整个对象不是真正的 "unique",那么 "set" 将不再有效。考虑到这一点,以及提到的操作符对于 2.6 之前的 MongoDB 版本不可用的事实,那么更传统的方法是 $unwind
the array members and then "filter" them via a $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
阶段,以减少 "possible" 匹配条件的文档。具有 $match
的 "second" 阶段在 "de-normalized" 形式中执行数组中文档元素的实际 "filtering"。
由于数组是$unwind
"deconstructed",$group
的目的是"re-build"数组,"filtered"来自不包含的元素符合条件。
MongoDB 还提供了 positional $
运算符,以便 select 匹配查询条件中的数组元素。像这样:
db.collection.find(
{ "products._id": { "$in": ["p1","p2","p3"] },
{ "details": 1, "products.$": 1 }
)
但这里的问题是这个运算符只支持 "first" 匹配查询文档中提供的条件。这是一种设计意图,目前还没有严格的运算符语法来满足多个匹配项的需求。
所以你目前的最终方法是使用 .aggregate()
方法来实际实现你想要的对内部数组的匹配过滤。要么过滤内容,要么在客户端代码中自行响应,具体取决于最终对您的口味。
我有一个名为 shops 的集合。结构如下:
[
{
'_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'
}
}
}
]
现在用户可以 select 菜单中的一些产品并尝试为这些产品找到商店。结果应该是所有提供至少一种 selected 商品的商店。
示例,
假设用户 select ['p1', 'p2', 'p3'] //ids
然后只有三个店
id1、id2、id3 将被列出(id4 有 none 个这些项目),加上结构是这样的,它从结果数组的文档中删除商店的其余产品(未列出)。
有没有办法,我可以直接从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"
}
}}
])
这与在聚合运算符中 "not so obvious" 使用 $or
一起使用。至少在语法和形式上是正确的,但它实际上是一个"complete fail"。原因是因为 $redact
通常是一个 "recursive" 操作,它检查文档的 "all levels" 而不是特定级别。因此,您在 "top level" 中的 _id
断言将失败,因为同名的顶级字段将不匹配该条件。
你真的没有什么可以做的,但考虑到数组中的 _id
实际上是一个 "unique" 元素,那么你总是可以在 $project
stage with the help of $map
and $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
运算符为每个文档处理数组 "inline" 并作用于每个元素以生成一个新数组。 false
在 $cond
下做出的条件不匹配的断言通过考虑与 $setDifference
相比的 "set" 结果来平衡,这实际上 "filters" false
结果数组的结果,只留下有效的匹配项。
当然,如果 _id
值或整个对象不是真正的 "unique",那么 "set" 将不再有效。考虑到这一点,以及提到的操作符对于 2.6 之前的 MongoDB 版本不可用的事实,那么更传统的方法是 $unwind
the array members and then "filter" them via a $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
阶段,以减少 "possible" 匹配条件的文档。具有 $match
的 "second" 阶段在 "de-normalized" 形式中执行数组中文档元素的实际 "filtering"。
由于数组是$unwind
"deconstructed",$group
的目的是"re-build"数组,"filtered"来自不包含的元素符合条件。
MongoDB 还提供了 positional $
运算符,以便 select 匹配查询条件中的数组元素。像这样:
db.collection.find(
{ "products._id": { "$in": ["p1","p2","p3"] },
{ "details": 1, "products.$": 1 }
)
但这里的问题是这个运算符只支持 "first" 匹配查询文档中提供的条件。这是一种设计意图,目前还没有严格的运算符语法来满足多个匹配项的需求。
所以你目前的最终方法是使用 .aggregate()
方法来实际实现你想要的对内部数组的匹配过滤。要么过滤内容,要么在客户端代码中自行响应,具体取决于最终对您的口味。