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

时间:2015-03-19 07:49:40

标签: javascript node.js mongodb aggregation-framework mongoskin

我有一个名为商店的系列。结构就像:

[
     {
          '_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得到这样的结果?

1 个答案:

答案 0 :(得分:2)

既然你确实提出了很好的问题并且形成得很好,那么有一些考虑,类似的答案实际上可能不适合参考,特别是如果你的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运算符为每个文档处理数组“内联”,并对每个元素进行操作以生成新数组。在$cond下根据条件不匹配的false断言通过考虑与$setDifference相比较的“设置”结果来平衡,false有效地“过滤”了_id来自结果数组的结果,只留下有效的匹配。

当然,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" } }} ]) 值或整个对象并非真正“唯一”,那么“集合”将不再有效。考虑到这一点,以及所提到的运算符在2.6之前的MongoDB版本不可用的事实,那么更传统的方法是$unwind数组成员然后通过{{3'过滤“它们操作。

$match

考虑到,根据其他示例,应首先在管道中执行$match阶段,以减少与条件匹配的“可能”文档。 $unwind的“第二”阶段在处于“非规范化”形式时对数组内的文档元素进行实际“过滤”。

由于数组是由db.collection.find( { "products._id": { "$in": ["p1","p2","p3"] }, { "details": 1, "products.$": 1 } ) “解构”的,$match的目的是“重新构建”数组,从与条件不匹配的元素中“过滤”。

MongoDB还提供$group运算符,以便从查询条件中选择匹配的数组元素。像这样:

{{1}}

但问题在于此运算符仅支持查询文档中提供的条件的“第一”匹配。这是一个设计意图,目前还没有严格的运算符语法来满足一个以上的匹配。

因此,您的最终方法是使用positional $方法,以便在您想要的内部数组上实际实现匹配过滤。无论是过滤内容还是过滤内容都会在客户端代码中自行响应,具体取决于最终对您的满意程度。