是否可以在动态嵌入式对象上使用聚合或$ sum?

时间:2018-06-26 17:41:17

标签: node.js mongodb mongoose mongodb-query

我有一个具有以下结构的集合:

{
  field1:'Foo',
  field2:11,
  stats:{
   A:10,
   B:15,
   C:10
 }
}

现在,我想总结统计字段的所有属性。 但是stats字段是Object,并且文档之间可能会有所不同(总是只有数字字段,但字段名称可以更改), 所以它看起来像这样:

{
  field1:'Foo2',
  field2:12,
  stats:{
   A:10,
   B:10,
   D:5
 }
}

有没有一种方法可以使用Aggregation或$ sum获得如下结果:

{
  sumStats:{
   A:20,
   B:25,
   C:10,
   D:5
 }
}

2 个答案:

答案 0 :(得分:1)

您可以尝试以下汇总:

db.col.aggregate([
    {
        $project: {
            stats: { $objectToArray: "$stats" }
        }
    },
    {
        $unwind: "$stats"
    },
    {
        $group: {
            _id: "$stats.k",
            value: { $sum: "$stats.v" }
        }
    },
    {
        $group: {
            _id: null,
            values: { $push: { k: "$_id", v: "$value" } }
        }
    },
    {
        $replaceRoot: {
            newRoot: { $arrayToObject: "$values" }
        }
    }
])

由于密钥未知,您需要$objectToArray才能将stats转换为键值对列表。然后,您可以使用$unwind为每个键值对获取单独的文档。在下一步中,您可以使用$group汇总所有文档中的值。结果,您需要一个对象,因此应按null分组以将一个对象中的所有值累加为键值对列表,然后可以使用$arrayToObject$replaceRoot来获得最终结构。

答案 1 :(得分:0)

您也可以尝试mapReduce聚合。文档说它可能效率不高,但是很容易阅读(这是使用Mongoose语法,但仅是Mongo驱动程序中同名函数的一个薄包装):

const mrd = {
  //creates an output bucket for each property in stats object
  map: function () { for (const prop in this.stats) emit(prop, this.stats[prop])},
  //sums all the values in the output bucket for each property
  reduce: function (k, vals) { return vals.reduce((accumulator, currentValue) => accumulator + currentValue, 0)}
};

YourModel.mapReduce(mrd, function (err, results) {
  console.log(results)
  //something like this (ran at Mongo console, assuming Mongoose output is similar): 
  //[{ "_id" : "a", "value" : 20 },
  //{ "_id" : "b", "value" : 25 },
  //{ "_id" : "c", "value" : 10 }
  //{ "_id" : "d", "value" : 5 }
})

输出不在一个对象中,但这很容易以直接代码(例如Array.mapReduce())完成;

参考: http://mongoosejs.com/docs/api.html#mapreduce_mapReduce https://docs.mongodb.com/manual/tutorial/map-reduce-examples/