4

在这个问题上我需要 mongo 的帮助:我有收集统计​​信息 (UserId, EventId, Count, Date) in collection are data

用户名 | 事件ID | 计数 | 日期

1     |     1    |    10  |   01.01.2012
1     |     1    |   15   | 01.02.2012
1     |     2    |   12   | 01.01.2012
2     |     1    |    5   |   01.01.2012
3     |     2    |    10  |   01.01.2012

我需要这个结果

用户名 | Count_Event_1 | Count_EventId_2

  1     |     25          |      12         
  2     |     5           |      0
  3     |     0           |      10

在没有 Map Reduce 的情况下,它可以在 Mongo 中使用吗?谢谢你的帮助。

4

2 回答 2

4

使用aggregate()!

我们将使用 a$project为每个事件创建一个计数器字段,如果事件匹配,则填写文档中的计数,否则为零。然后我们将$group通过 user-id,汇总所有事件计数器。

为了解释起见,让我首先展示在您的示例中,这对于两个不同的事件(1 和 2)是如何硬编码的:

db.xx.aggregate([
    { $project: { userid:1,
                  cnt_e1: { $cond: [ { $eq: [ "$event", 1 ] }, "$count", 0 ] },
                  cnt_e2: { $cond: [ { $eq: [ "$event", 2 ] }, "$count", 0 ] },
    } },
    { $group: { _id: "$userid", cnt_e1: { $sum: "$cnt_e1" }, cnt_e2: { $sum: "$cnt_e2" } } },  
    { $sort: { _id: 1 } },
])

对于给定的集合:

> db.xx.find({},{_id:0})
{ "userid" : 1, "event" : 1, "count" : 10 }
{ "userid" : 1, "event" : 1, "count" : 15 }
{ "userid" : 1, "event" : 2, "count" : 12 }
{ "userid" : 2, "event" : 1, "count" : 5 }
{ "userid" : 3, "event" : 2, "count" : 10 }

结果是:

{
    "result" : [
        {
            "_id" : 1,
            "cnt_e1" : 25,
            "cnt_e2" : 12
        },
        {
            "_id" : 2,
            "cnt_e1" : 5,
            "cnt_e2" : 0
        },
        {
            "_id" : 3,
            "cnt_e1" : 0,
            "cnt_e2" : 10
        }
    ],
    "ok" : 1
}

要为可变事件完成此操作,我们必须生成投影和分组。我们将使用该命令获取所有可能事件的数组distinct()(您可能希望在“事件”上定义一个索引)。然后我们通过遍历数组将这两个语句创建为 JSON 对象:

project = {};
project.$project = {};
project.$project.userid = 1;

group = {};
group.$group = {};
group.$group._id = '$userid'

events = db.xx.distinct( "event" );
events.forEach( function( e ) {
    field = "cnt_e" + e;

    eval("project.$project." + field + " = {}");
    eval("project.$project." + field + ".$cond = []");
    eval("project.$project." + field + ".$cond[0] = {}");
    eval("project.$project." + field + ".$cond[0].$eq = []");
    eval("project.$project." + field + ".$cond[0].$eq[0] = '$event'");
    eval("project.$project." + field + ".$cond[0].$eq[1] = " + e );
    eval("project.$project." + field + ".$cond[1] = '$count'");
    eval("project.$project." + field + ".$cond[2] = 0");

    eval("group.$group." + field + " = {}");
    eval("group.$group." + field + ".$sum = '$" + field + "'");
});

//printjson(project);
//printjson(group);

db.xx.aggregate([
    project,
    group,
    { $sort: { _id: 1 } },
])

结果和上面一样。

注意:以上适用于数字事件。如果它们是字符串,则必须调整生成器。

乍一看,这可能看起来比 @Philipp 的 mapReduce 更复杂。但这不会返回每个用户的所有事件 - 只有那些确实有计数的事件。对于完整的垂直到水平映射,您还必须生成 map 和 reduce 函数。

有关聚合()的更多信息,请参阅http://docs.mongodb.org/manual/aggregation/

于 2013-04-07T09:10:16.183 回答
0

您必须使用MapReduce操作来执行此操作。

您的地图功能将如下所示:(未经测试!):

var mapFunction = function() {
                   var ret = {};
                   ret["Count_Event_" + this.EventId] = this.Count;
                   emit(this.UserId, ret);
               };

这会发出一系列由 UserId 和一个对象组成的对,该对象具有一个以计数作为值的不同名称属性。

然后,您的 reduce 函数会将结果合并为一个(未经测试 - 我不确定您是否可以只增加一个不存在的属性,我现在无法对其进行测试):

var reduceFunction = function(UserId, values_array) {
                   var ret = {};

                   for (var i = 0; i < values_array.length; i++) {
                       var values = values_array[i];
                       for (key in values) {
                           ret[key] += values[key]; // Can you increment a non-existing attribute? Not sure, try it, please.
                       }
                   }                       

                   return ret;
               };

然后你这样称呼它:

 db.yourCollection.mapReduce(
                 mapFunction,
                 reduceFunction,
                 out: { inline: 1 }
               )

该行将out: { inline: 1 }结果输出到控制台。通常你使用 MapReduce 来创建一个带有结果的新集合。有关更多信息,请参阅文档

于 2013-03-19T12:26:50.237 回答