Mongodb:如何返回查询列表中存在的数组元素
我有一个名为商店的集合。 结构如下:
[ { '_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一些产品,并尝试find那些商店。 结果应该是提供至less一个所选项目的所有商店。
例,
假设用户select['p1', 'p2', 'p3'] //ids
那么只有三个商店id1,id2,id3将被列出(id4没有这些项目),加上结构是这样的,结果数组中的文档中的商店产品(未列出)。
有没有办法,我可以直接从mongodb得到这样的结果?
既然你提问得很好,而且forms也很好,那么有一些考虑,类似的答案可能实际上并不适合参考,特别是如果你的MongoDB产品的经验水平很低。
像$redact
这样的选项可能看起来很简单,而且通常也很适合。 但是,如何构build语句并不是一个例子:
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
聚合操作符一起工作。 至less在正确的语法和forms方面,但它实际上是一个“完全失败”。 原因在于, $redact
通常是一个“recursion”操作,它在文档的“所有级别”进行检查,而不是在特定的级别进行检查。 因此,在“顶层”中, _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
操作符为每个文档“内联”处理数组,并对每个元素进行操作以生成一个新数组。 在condtions不匹配的$cond
下做出的false
断言通过考虑与$setDifference
相比较的结果“集合”来平衡,该集合有效地“过滤”来自结果数组的false
结果,仅留下有效的匹配。
当然,如果_id
值或整个对象不是真正的“唯一”,那么“集合”将不再有效。 有了这个考虑,以及上述操作符在2.6以前版本的MongoDB中不可用的事实,更传统的方法是$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
阶段应该首先在pipe道中执行,以便减less与条件匹配的“可能的”文档。 带有$match
的“第二”阶段在处于“非归一化”forms时实际上“过滤”数组内的文档元素。
由于数组被$unwind
“解构”,所以$group
是“重新构build”数组,从与条件不匹配的元素中“过滤”。
为了从查询条件中select匹配的数组元素,MongoDB还提供了位置$
操作符。 像这样:
db.collection.find( { "products._id": { "$in": ["p1","p2","p3"] }, { "details": 1, "products.$": 1 } )
但是这里的问题是这个操作符只支持在查询文档中提供的条件上的“第一个”匹配。 这是一个devise意图,至今还没有严格的操作符语法来满足一个以上的匹配。
所以你最终的方法是使用.aggregate()
方法来实际地实现你所期望的内部数组的匹配过滤。 无论是或者过滤内容在客户端代码自己回应,取决于最终对你的可口性。