如何在 MongoDb 中聚合非关联数组中的数据

How to aggregate data within non associative array in MongoDb

问这个问题之前查了很多资料,都没有找到相关资料

这里是:

我的问题是我有一个 mongo 集合,格式如下:

{
    "_id" : ObjectId("55bf4031eb8ac118a4b3110e"),
    "sid" : 1,
    "plugin_count" : [
      0,
      0,
      0,
      1,
      0,
      1,
      0,
      0,
      0, 
      0
    ],

},
{
    "_id" : ObjectId("55bf4031eb8ac118a4b3110e"),
    "sid" : 1,
    "plugin_count" : [
      2,
      1,
      0,
      6,
      0,
      10,
      12,
      0,
      16, 
      22
    ],

}

现在我想做的是,我想单独添加非关联数组中存在的所有元素并像这样输出它们:

{
    "result": [
        {
            "_id": 1,
            "plugin_count": [
                2,
                1,
                0,
                7,
                0,
                11,
                12,
                0,
                16,
                22
            ]
        }
    ],
    "ok": 1
}

我的查询如下

db.plugin_table.aggregate([
    {
        "$match" : {
            "sid" : {
                "$in" : [1]
            }
        }
    },
    {
        "$unwind" : "$plugin_count"
    },
    {
        "$group" : {
            "_id": 1,
            "plugin_count" : {
                "$sum" : "$plugin_count"
            }
        }
    }
]);

但我得到以下输出:

{
    "result": [
        {
            "_id": 1,
            "plugin_count": 0
        }
    ],
    "ok": 1
}

请帮帮我,我简直是在拔头发。 :(

使用聚合框架确实没有明智的方法来做到这一点。这里的问题是跟踪数组元素的 "index" 位置,其中没有什么可以做到这一点。

你最好的选择是 mapReduce,这可以很简单地处理问题,并且可以很好地扩展到任意数量的分组键:

db.plugin_table.mapReduce(
    function () {
      emit(this.sid, { plugin_count: this.plugin_count });
    },
    function (key,values) {
      var result = { plugin_count: [] };

      values.forEach(function(value) {
        value.plugin_count.forEach(function(plugin,idx) {
          if ( result.plugin_count[idx] === undefined ) {
            result.plugin_count[idx] = plugin;
          } else {
            result.plugin_count[idx] += plugin;
          }
        });
      });

      return result;
    },
    { 
      "query": { "sid": 1 },
      "out": { "inline": 1 }
    }
)

这会生成所需的输出,尽管 mapReduce 总是生成具有 _idvalue:

的顶级键的输出形式
            {
                    "_id" : 1,
                    "value" : {
                            "plugin_count" : [
                                    2,
                                    1,
                                    0,
                                    7,
                                    0,
                                    11,
                                    12,
                                    0,
                                    16,
                                    22
                            ]
                    }
            }

请注意,这里的 reduce 算法也可以轻松处理不同长度的数组,因为所有内容都按索引位置配对,或者如果该位置尚未出现在每个键的组合结果中,则以其他方式创建。


唯一真正聚合框架可以处理这个问题的方法是已经将数据实际放在"associative array"中,或者至少将每个元素作为关联数组本身,像这样:

{
    "_id" : ObjectId("55d32ffde12af47feb19bce7"),
    "sid" : 1,
    "plugin_count" : [
            {
                    "pos" : 0,
                    "value" : 0
            },
            {
                    "pos" : 1,
                    "value" : 0
            },
            {
                    "pos" : 2,
                    "value" : 0
            },
            {
                    "pos" : 3,
                    "value" : 1
            },
            {
                    "pos" : 4,
                    "value" : 0
            },
            {
                    "pos" : 5,
                    "value" : 1
            },
            {
                    "pos" : 6,
                    "value" : 0
            },
            {
                    "pos" : 7,
                    "value" : 0
            },
            {
                    "pos" : 8,
                    "value" : 0
            },
            {
                    "pos" : 9,
                    "value" : 0
            }
    ]
}

这是沿着这些方向的基本转换:

  db.junk.find().forEach(function(doc) { 
    doc.plugin_count = doc.plugin_count.map(function(value,idx) { 
      return { "pos": idx, "value": value };
    });
    db.newjunk.insert(doc);
  });

然后你有一个基本的聚合,通过简单地在 "pos" 元素上分组 "first" 并对结果求和。然后可以通过分组返回 "sid":

来形成最终数组
  db.newjunk.aggregate([
    { "$match": { "sid": 1 } },
    { "$unwind": "$plugin_count" },
    { "$group": {
      "_id": {
        "sid": "$sid",
        "pos": "$plugin_count.pos"
      },
      "value": { "$sum": "$plugin_count.value" }
    }},
    { "$sort": { "_id": 1 } },
    { "$group": {
      "_id": "$_id.sid",
      "plugin_count": { "$push": "$value" }
    }}
  ])

这会为您提供与之前相同的输出:

{ "_id" : 1, "plugin_count" : [ 2, 1, 0, 7, 0, 11, 12, 0, 16, 22 ] }

还要注意,您可以避免 $sort stage here by keeping the associative information. Using $group 并不能保证位置保持不变,但是对于关联信息,这并不是真正需要的。

所以这一切都取决于你能忍受什么。如果您想将普通数组保留在数据中,那么您将需要 mapReduce 来获取结果。但是如果你愿意改变数据格式,那么聚合方法是完全可以的。

然而,这可能只是一个很好的案例,在 "large scale" 时,mapReduce 进程将通过避免处理 $unwind.

的开销来击败聚合进程