如何比较数组和计数匹配项

时间:2018-04-22 09:37:08

标签: javascript mongodb mongodb-query aggregation-framework

我有一个包含以下文件的集合:

$owner_questions

现在我想要的是根据以下数组项计算文档总数:

 {
   _id: ObjectId("000000000000000000059734"),
   locations: ["A", "B", "C"]  
 },

 {
   _id: ObjectId("000000000000000000059735"),
   locations: ["A", "D", "K"]  
 },

 {
   _id: ObjectId("000000000000000000059736"),
   locations: ["1", "3", "C"]  
 }

我想要的结果是:

let array = ['A', 'B', '1'];

我尝试过:

{
  'A': 2,
  'B': 1,
  '1': 1
}

但我的查询结果格式与我想要的不一样。

感谢任何帮助和指导。

2 个答案:

答案 0 :(得分:1)

如果你至少有MongoDB 3.4.4,那么你可以这样做:

var array = ['A', 'B', '1'];

db.getCollection('mycollection').aggregate([
  { "$project": {
    "locations": {
      "$map": {
        "input": {
          "$filter": {
            "input": "$locations",
            "cond": { "$in": [ "$$this", array ] }
          }
        },
        "in": { "k": "$$this", "v": 1 }
      }
    }
  }},
  { "$unwind": "$locations" },
  { "$group": {
    "_id": "$locations.k",
    "v": { "$sum": "$locations.v" }
  }},
  { "$sort": { "_id": 1 } },
  { "$group": {
     "_id": null,
     "obj": { "$push": { "k": "$_id", "v": "$v" } } 
  }},
  { "$replaceRoot": {
    "newRoot": { "$arrayToObject": "$obj" }  
  }}
])

对于没有$arrayToObject之类的旧版本,您可以在从服务器返回结果后“转换”结果,如下所示:

var array = ['A', 'B', '1'];

db.getCollection('mycollection').aggregate([
  { "$project": {
    "locations": {
      "$map": {
        "input": {
          "$filter": {
            "input": "$locations",
            "cond": {
              // "$in": [ "$$this", array ]
              "$or": array.map(a => ({ "$eq": [ "$$this", a ] }) )
            }
          }
        },
        "in": { "k": "$$this", "v": 1 }
      }
    }
  }},
  { "$unwind": "$locations" },
  { "$group": {
    "_id": "$locations.k",
    "v": { "$sum": "$locations.v" }
  }},
  { "$sort": { "_id": 1 } },
  { "$group": {
     "_id": null,
     "obj": { "$push": { "k": "$_id", "v": "$v" } } 
  }},
  /*
  { "$replaceRoot": {
    "newRoot": { "$arrayToObject": "$obj" }  
  }}
  */
]).map(d => 
  d.obj.reduce((acc,curr) => Object.assign(acc,{ [curr.k]: curr.v }),{})
)

在任何一种情况下,第一阶段都是$project $map,以便查看文档数组中的每个值并将其与比较数组进行比较。实际上,我们使用$filter只返回“匹配”,$map返回值1来计算每次匹配。

对于支持运算符的版本,使用$in进行“过滤”有两种基本方法,或者在引入之前使用旧版本中的$or

坦率地说,只要您的文档数据是“唯一的”,就可以简单地使用$setIntersection来获取匹配项,因为没有文档数组包含多个值的出现。所以我在这里玩$filter安全,因为我不知道你的数据。选择适合的任何一种。

 // If the "locations" content is meant to be "unique"
 { "$project": {
    "locations": {
      "$map": {
        "input": {
          "$setIntersection": [ "$locations", array ]
        },
        "in": { "k": "$$this", "v": 1 }
      }
    }
  }},

请注意kv属性表单中的$map输出。这将继续作为管道其余部分的模式。

由于您希望对数组项的k值进行“聚合”,因此我们使用$unwind,因此我们可以跨文档将这些值一起添加。然后通过$groupk的值与每个v上的$sum相关联,以有效地“计算”这些事件。

$sort完全是可选的,实际上你不应该关心单个输出文档中键的顺序。请注意与“欲望”的区别,但这只是一个明显的事实,即"1"在词汇上“少于”"A"。所以你无法与之抗争,而这正是世界的运作方式。

下一阶段只需$group到单个文档即可。在这里,我们继续重建为包含kv的对象的“数组”。

原因在于最终处理。您拥有支持$arrayToObject的MongoDB的地方(自3.4.4以来实际包含,但文档声明3.6)。在你做的地方,我们只是在$replaceRoot阶段内提供生成的“数组”作为输入,以便返回最终输出。

如果您没有该功能,则可以处理光标结果(此处使用shell Cursor.map()显示)并在进一步处理之前转换文档。任何迭代器方法都可以,大多数驱动程序都有Cursor.map()。这并不重要,因为在这种情况下聚合管道会产生一个文档。

在现代shell版本中使用的JavaScript方法是简单地在数组上应用.reduce()并将输出对象转换为所需的对象输出。它基本上与服务器完全相同,但只是在客户端代码中。

任何一种形式都会返回所需的结果:

{
    "1" : 1.0,
    "A" : 2.0,
    "B" : 1.0
}

答案 1 :(得分:0)

db.mycollection.aggregate(

    // Pipeline
    [
        // Stage 1
        {
            $unwind: {
                path: '$locations'
            }
        },

        // Stage 2
        {
            $match: {
                locations: {
                    $in: ['A', 'B', '1']
                }
            }
        },

        // Stage 3
        {
            $group: {
                _id: '$locations',
                total: {
                    $sum: 1
                }
            }
        }

    ]


);