简体   繁体   English

MongoDB:如何返回查询列表中存在的数组元素

[英]Mongodb: how to return elements of array that are present in the query list

I have a collection called shops. 我有一个叫做商店的收藏。 Structure is like: 结构就像:

[
     {
          '_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'
               }
          }
     }
]

Now user can select some of the products from menu and try to find the shops for those. 现在,用户可以从菜单中选择一些产品,然后尝试找到这些商店。 The result should be all the shops which provide atleast one of the selected items. 结果应该是所有提供至少一项选定商品的商店。

Example, 例,

Suppose users select ['p1', 'p2', 'p3'] //ids Then only three shops id1, id2, id3 will be listed(id4 has none of these items), plus the structure is such that it removes rest of the products of a shop(which were not listed) from the document in the results array. 假设用户选择['p1', 'p2', 'p3'] //ids那么只会列出三个商店id1,id2,id3(id4没有这些项目),加上这样的结构可以删除其余的结果数组中来自文档的商店产品(未列出)。

Is there a way, I can get such result from mongodb directly? 有没有办法,我可以直接从mongodb获得这样的结果?

Since you did ask nicely and also so well formed then there is some consideration that similar answers may not actually suit for reference, especially if your experience level with the MongoDB product is low. 由于您确实提出了很好的要求,而且格式也很合理,因此需要考虑一些类似的答案可能实际上并不适合作为参考,特别是如果您对MongoDB产品的经验水平很低时。

Options like $redact may seem simple, and they are often well suited. $redact类的选项似乎很简单,通常很适合。 But this is not a case for how you would need to construct the statement: 但这不是您需要如何构造语句的情况:

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"
    }
  }}
])

That works with the "not so obvious" use of $or in an aggregation operator. 这与$or在聚合运算符中的“不太明显”用法一起使用。 At least in terms of the correct syntax and form, but it is actually a "complete fail". 至少就正确的语法和形式而言,但这实际上是“完全失败”。 The reasoning is that because $redact is generally a "recursive" operation, and it inspects at "all levels" of the document and not just at a specific level. 原因是因为$redact通常是“递归”操作,并且它在文档的“所有级别”进行检查,而不仅仅是在特定级别进行检查。 So of your in the "top level" the _id assertion will fail as that top level field of the same name is not going to match that condition. 因此,在您的“顶层”中, _id断言将失败,因为同名的顶层字段将不符合该条件。

There really isn't anything else you can really do about this, but considering that _id in the array is actually a "unique" element then you can always perform this operation in an $project stage with the help of $map and $setDifference : 实际上,您没有任何其他可以做的事情,但是考虑到数组中的_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]
      ]
    }
  }}
])

It seems lengthy, but it actually very efficient. 似乎很长,但实际上非常有效。 The $map operator processes arrays "inline" for each document and acting on each element to produce a new array. $map运算符为每个文档“内联”处理数组,并作用于每个元素以产生新的数组。 The false assertion made under $cond where the condtions are not a match is balanced by considering the "set" of results in comparison to $setDifference , which effectively "filters" the false results from the resulting array, leaving only the valid matches behind. $setDifference相比,通过考虑结果的“集合”,可以平衡在条件不匹配的$cond下作出的false断言,后者可以有效地“过滤”结果数组中的false结果,仅保留有效的匹配项。

Of course where the _id values or entire objects were not truly "unique" then a "set" would no longer be valid. 当然,如果_id值或整个对象不是真正的“唯一”,则“集合”将不再有效。 With this consideration, as well as the truth that the mentioned operators are not available to versions of MongoDB prior to 2.6, then the more tradtional approach is to $unwind the array members and then "filter" them via a $match operation. 考虑到这一点,以及上述提到的运算符不可用于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" }
  }}
])

Consideration is given that as per the other examples, the $match phase should be executed first in the pipeline in order to reduce the "possible" documents matching the condition. 考虑到按照其他示例, $match阶段应首先在管道中执行,以减少匹配条件的“可能”文档。 The "second" phase with $match does the actuall "filtering" of the document elements inside the array when in the "de-normalized" form. 具有$match的“第二”阶段以“非规范化”形式对数组内的文档元素进行实际的“过滤”。

Since the array was "deconstructed" by $unwind , the purpose of $group is to "re-build" the array, "filtered" from the elements that do not match the condition. 由于数组是由$unwind “解构”的,因此$group的目的是“重建”数组,并从与条件不匹配的元素中“过滤”出来。

MongoDB also offers the positional $ operator in order to select matched array elements from a query condition. MongoDB还提供了位置$运算符,以便从查询条件中选择匹配的数组元素。 Like so: 像这样:

db.collection.find(
    { "products._id": { "$in": ["p1","p2","p3"] },
    { "details": 1, "products.$": 1 }
)

But the problem here is that this operator only supports the "first" match on the conditions supplied in the query document. 但是这里的问题是,此运算符仅在查询文档中提供的条件上支持“第一”匹配。 This is a design intent, and as yet there is no strict operator syntax to cater for more than a single match. 这是设计目的,到目前为止,还没有严格的运算符语法可以满足多个匹配项。

So your ultimate approach is currently to use the .aggregate() method in order to actually achieve the match filtering on inner arrays that you desire. 因此,您的最终方法是当前使用.aggregate()方法,以便在所需的内部数组上实际实现匹配过滤。 Either that or filter the contents responded yourself in client code, depending on how palatable that ultimately is to you. 要么过滤内容,要么在客户端代码中响应自己,这取决于最终对您的可口性。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM