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

时间:2015-08-18 13:09:50

标签: mongodb mapreduce mongodb-query associative-array aggregation-framework

我在问这个问题之前经常搜索,但找不到任何相关信息。

所以这是:

我的问题是我有以下格式的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
}

请帮助我从字面上拉出我的头发。 :(

1 个答案:

答案 0 :(得分:0)

使用聚合框架确实没有理智的方法。这里的问题是跟踪数组元素的“索引”位置,其中没有任何东西可以做到这一点。

这里最好的选择是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 }
    }
)

这会生成所需的输出,albiet格式为mapReduce始终使用顶级键_idvalue生成输出:

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

请注意,此处的reduce算法也可以轻松处理不同长度的数组,因为所有内容都与索引位置配对,或者如果每个键的组合结果中不存在该位置,则会创建其他数据。

聚合框架可以处理的唯一真实方式是将数据实际上放在“关联数组”中,或者至少将每个元素作为关联数组本身,就像这样:

{
    "_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阶段。使用$group并不能保证位置保持不变,但使用关联信息并不是必需的。

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

然而,这可能是一个很好的例子,在“大规模”的情况下,mapReduce进程可以通过避免处理$unwind的开销来击败聚合过程。