【问题标题】:Aggregate analytics data by day/next day logic按日/次日逻辑聚合分析数据
【发布时间】:2017-06-23 19:51:26
【问题描述】:

我为移动应用分析编写了一个服务器,其中有一个分片 (!Upd) 集合,其中包含如下事件:

{
  "event": "install",
  "userId": "a",
  "time": 2014-02-09,
  "data" : ...
},
{
  "event": "login",
  "userId": "a",
  "time": 2014-02-12,
  "data" : ...
},
{
  "event": "install",
  "userId": "b",
  "time": 2014-4-29,
  "data" : ...
},
{
  "event": "login",
  "userId": "b",
  "time": 2014-4-30,
  "data" : ...
}
...

我需要选择在install事件之后的第二天有事件install而不是login的用户(换句话说,我想选择安装应用程序但不登录的用户明天)。所以上述数据的输出应该是:

{
   "userId": "a",
   "data" : ...
}

如何使用聚合框架或 mapreduce 执行此任务?或者也许是另一种解决方案?

【问题讨论】:

    标签: mongodb aggregation-framework


    【解决方案1】:

    这有点棘手:-) 如果时间只是一个日期字段(没有时间数据),您可以通过聚合来完成, 然后 有收藏

    {
        "_id" : ObjectId("57694365ef9176ec54960a66"),
        "event" : "install",
        "userId" : "a",
        "time" : ISODate("2014-09-02T00:00:00.000Z")
    },{
        "_id" : ObjectId("57694365ef9176ec54960a67"),
        "event" : "login",
        "userId" : "a",
        "time" : ISODate("2014-12-02T00:00:00.000Z")
    },{
        "_id" : ObjectId("57694365ef9176ec54960a68"),
        "event" : "install",
        "userId" : "b",
        "time" : ISODate("2014-04-29T00:00:00.000Z")
    },{
        "_id" : ObjectId("57694365ef9176ec54960a69"),
        "event" : "login",
        "userId" : "b",
        "time" : ISODate("2014-04-30T00:00:00.000Z")
    }
    

    我们可以使用聚合查询:

    var match = {
        $match : {
            "event" : "install"
        }
    };
    
    var projectNextDayDate = {
        $project : {
            _id : 1,
            event : 1,
            userId : 1,
            time : 1,
            nextDay : {
                $add : ["$time", 24 * 60 * 60 * 1000]
            }
    
        }
    }
    
    var lookup = {
        $lookup : {
            from : "zella",
            localField : "nextDay",
            foreignField : "time",
            as : "mergedDocs"
        }
    }
    
    var nowMatchUsers = {
        $project : {
            _id : 1,
            event : 1,
            userId : 1,
            time : 1,
            nextDay : 1,
            mergedDocs : {
                $filter : {
                    input : "mergedDocs",
                    as : "m",
                    cond : {
    
                        $eq : ["$$m.userId", "$userId"]
                    }
                }
            }
        }
    }
    
    var findEmptyArrays = {
        $match : {
            mergedDocs : []
        }
    }
    db.zella.aggregate([match, projectNextDayDate, lookup, findEmptyArrays])
    

    这个输出:

    {
        "_id" : ObjectId("57694365ef9176ec54960a66"),
        "event" : "install",
        "userId" : "a",
        "time" : ISODate("2014-09-02T00:00:00.000Z"),
        "nextDay" : ISODate("2014-09-03T00:00:00.000Z"),
        "mergedDocs" : []
    }
    

    这里的假设是时间是一个日期2014-09-02T00:00:00.000 合并集合的另一种方法是使用用户 ID,作为 $lookup 点,但随后将有更多逻辑来过滤结果集并且可能会降低性能。

    【讨论】:

    • 谢谢,解决方案有效,但lookup 不支持分片集合,所以现在我尝试了解 chridam 解决方案,这似乎有点棘手:)
    • 哈哈 :-) 该信息应该在问题中 :-),克里斯托弗的回答将适用于分片集合
    • 对不起,当我问问题时,我不知道这个限制。
    • 没问题 :-) 我们都是人类,学习如何处理 mongo :D
    【解决方案2】:

    您可以尝试运行以下聚合管道:

    db.test.aggregate([
        {
            "$project": {
                "event": 1,
                "userId": 1,
                "time": 1,
                "data": 1,
                "dayAfter": {
                    "$add": [ "$time", 24 * 60 * 60 * 1000 ]
                }
            }
        },
        { "$match": { "event": { "$in": ["install", "login"] } } },
        {
            "$group": {
                "_id": "$userId",
                "eventsTimeLine": {
                    "$push": {
                        "event": "$event",
                        "time": "$time",
                        "dayAfter": "$dayAfter"
                    }
                },
                "data": { "$push": "$data" }
            }
        },
        { "$unwind": "$eventsTimeLine" },
        { "$sort": { "eventsTimeLine.event": 1 } },
        {
            "$group": {
                "_id": "$_id",
                "dayAfterInstall": { "$first": "$eventsTimeLine.dayAfter" },
                "loginTime": { "$last": "$eventsTimeLine.time" },
                "data": { "$first": "$data" }
            }
        },
        { 
            "$project": { 
                "isChurn": { "$ne": [ "$loginTime", "$dayAfterInstall" ] },
                "userId": "$_id", "data": 1, "_id": 0 
            } 
        },
        { "$match" : { "isChurn" : true } }
    ])
    

    【讨论】:

      【解决方案3】:

      这里是另一个使用 mapreduce 和聚合的解决方案:

      var mapFunction = function() {
      
                            if (this.event != 'install' && this.event != 'login'){
                              return;
                            }
      
                            var value = {data: this.data, count: 1};
      
                            if (this.event == 'install'){
      
                               var nextDay = new Date(this.date.getTime() + 24 * 60 * 60 * 1000)
      
                               emit({userId:this.userId, nextDayAfterInstall:nextDay}, value );
                            } else
                            if (this.event == 'login'){
                                emit({userId:this.userId, nextDayAfterInstall:this.date}, value );
                            }
      
      
                         };
      
      
      var reduceFunction = function(event, values) {
      
                              var value = { data: null, count: 1 };
      
                               for (var index = 0; index < values.length; ++index) {
                                  value.count += values[index].count;
                                  value.data = values[index].data;
                               }
      
                               return value ;
                            };           
      
      db.events.mapReduce(
                           mapFunction,
                           reduceFunction,
                           { out: "case1_mr_out" }
                         )         
      
      
      var groupByUserId = {
          $group : 
          { 
              _id : { userId: "$_id.userId" },
              data : { $last: '$value.data' },
              count : { $max: '$value.count' }
          } 
      }       
      
      var filterWhereOnlyOne = {
          $match : {
              "count" : 1
          }
      };            
      
      db.case1_mr_out.aggregate([groupByUserId,filterWhereOnlyOne])   
      

      【讨论】:

        猜你喜欢
        • 2017-10-02
        • 1970-01-01
        • 1970-01-01
        • 2016-04-07
        • 2013-11-25
        • 1970-01-01
        • 1970-01-01
        • 2021-02-05
        • 1970-01-01
        相关资源
        最近更新 更多