【问题标题】:Mongodb: how to return elements of array that are present in the query listMongodb:如何返回查询列表中存在的数组元素
【发布时间】:2015-05-22 06:16:29
【问题描述】:

我有一个名为“商店”的集合。结构如下:

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

【问题讨论】:

  • 查看答案列表.aggregate(),也可能在this question的回复下方查看$redact
  • 好的,我会检查的
  • $redact 应该适用于剥离结果,但我如何只搜索那些在查询列表中至少包含一个元素的文档?
  • 查看文档$match 用于一般查询,$in 运算符用于查询选择以及dot notation。特别是{ "$match": { "products._id": { "$in": ["p1","p2","p3"] } }}。有关聚合管道的进一步阅读也在核心文档中。以及这里的许多示例aggregation-framework
  • 我认为这是我的工作。谢谢。

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


【解决方案1】:

由于您确实问得很好,而且格式也很好,所以有一些考虑是类似的答案实际上可能不适合作为参考,特别是如果您对 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 值或整个对象不是真正“唯一”的,那么“集合”将不再有效。考虑到这一点,以及上述运算符在 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 阶段应首先在管道中执行,以减少匹配条件的“可能”文档。 $match 的“第二”阶段在“去规范化”形式时对数组内的文档元素进行实际“过滤”。

由于数组被$unwind“解构”,$group 的目的是“重新构建”数组,从不符合条件的元素中“过滤”出来。

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

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

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

因此,您目前的最终方法是使用.aggregate() 方法来实际实现您想要的内部数组的匹配过滤。要么过滤内容,要么在客户端代码中响应您自己,这取决于最终对您来说有多好。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-14
    • 2016-03-24
    • 1970-01-01
    相关资源
    最近更新 更多