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()方法来实际地实现你所期望的内部数组的匹配过滤。 无论是或者过滤内容在客户端代码自己回应,取决于最终对你的可口性。