【问题标题】:MongoDB MapReduce - How to populate an array in reduce function?MongoDB MapReduce - 如何在reduce函数中填充数组?
【发布时间】:2016-10-05 07:39:05
【问题描述】:

我有一个包含 userId、movieId、movie-categoryId、reviewId、movieRating 和 reviewDate 列的 MovieRatings 数据库。

在我的映射器中,我想提取 userId -> (movieId, movieRating)

然后在减速器中,我想按用户对所有 movieId、movieRating 对进行分组。

这是我的尝试:

地图功能:

var map = function() {
    var values={movieId : this.movieId, movieRating : this.movieRating};
    emit(this.userId, values);}

减少功能:

var reduce = function(key,values) {
    var ratings = [];
    values.forEach(function(V){
        var temp = {movieId : V.movieId, movieRating : V.movieRating};
        Array.prototype.push.apply(ratings, temp);
        });
    return {userId : key, ratings : ratings };
}

运行 MapReduce:

db.ratings.mapReduce(map, reduce, { out: "map_reduce_step1" })

输出:db.map_reduce_step1.find()

{ "_id" : 1, "value" : { "userId" : 1, "ratings" : [ ] } } 
{ "_id" : 2, "value" : { "userId" : 2, "ratings" : [ ] } } 
{ "_id" : 3, "value" : { "userId" : 3, "ratings" : [ ] } } 
{ "_id" : 4, "value" : { "userId" : 4, "ratings" : [ ] } } 
{ "_id" : 5, "value" : { "userId" : 5, "ratings" : [ ] } } 
{ "_id" : 6, "value" : { "userId" : 6, "ratings" : [ ] } } 
{ "_id" : 7, "value" : { "userId" : 7, "ratings" : [ ] } } 
{ "_id" : 8, "value" : { "userId" : 8, "ratings" : [ ] } } 
{ "_id" : 9, "value" : { "userId" : 9, "ratings" : [ ] } } 
{ "_id" : 10, "value" : { "userId" : 10, "ratings" : [ ] } } 
{ "_id" : 11, "value" : { "userId" : 11, "ratings" : [ ] } } 
{ "_id" : 12, "value" : { "userId" : 12, "ratings" : [ ] } } 
{ "_id" : 13, "value" : { "userId" : 13, "ratings" : [ ] } } 
{ "_id" : 14, "value" : { "userId" : 14, "ratings" : [ ] } } 
{ "_id" : 15, "value" : { "movieId" : 1, "movieRating" : 3 } } 
{ "_id" : 16, "value" : { "userId" : 16, "ratings" : [ ] } }

我没有得到预期的输出。事实上,这个输出对我来说毫无意义!

这是我在减速器中尝试做的 python 等价物(以防减速器的目的在上面不清楚):

def reducer_ratings_by_user(self, user_id, itemRatings):
        #Group (item, rating) pairs by userID
        ratings = []
        for movieID, rating in itemRatings:
            ratings.append((movieID, rating))
        yield user_id, ratings

编辑 1 @chridam

这是我真正想做的事情的大纲:

Movies.csv 文件如下所示:

userId,movieId,movie-categoryId,reviewId,movieRating,reviewDate
1,1,1,1,5,7/12/2000
2,1,1,2,5,7/12/2000
3,1,1,3,5,7/12/2000
4,1,1,4,4,7/12/2000
5,1,1,5,4,7/12/2000
6,1,1,6,5,7/15/2000
1,2,1,7,4,7/25/2000
8,1,1,8,4,7/28/2000
9,1,1,9,3,8/3/2000
...
...

我将它导入 mongoDB:

mongoimport --db SomeName --collection ratings --type csv --headerline --file Movies.csv 

然后我尝试应用上面定义的 map-reduce 函数。之后,我将通过执行以下操作将其导出回 csv:

mongoexport --db SomeName --collection map_reduce_step1 --csv --out movie_ratings_out.csv --fields ...

这个movie_ratings_out.csv 文件应该是这样的:

userId、movieId1、rating1、movieId2、rating2、...
1,1,5,2,4
...
...

所以每一行都包含每个用户的所有 (movie,rating) 对。

编辑 2

示例:

db.ratings.find().pretty()
{
    "_id" : ObjectId("57f4a0dd9cb74fc4d344a40f"),
    "userId" : 4,
    "movieId" : 1,
    "movie-categoryId" : 1,
    "reviewId" : 4,
    "movieRating" : 4,
    "reviewDate" : "7/12/2000"
}
{
    "_id" : ObjectId("57f4a0dd9cb74fc4d344a410"),
    "userId" : 5,
    "movieId" : 1,
    "movie-categoryId" : 1,
    "reviewId" : 5,
    "movieRating" : 4,
    "reviewDate" : "7/12/2000"
}
{
    "_id" : ObjectId("57f4a0dd9cb74fc4d344a411"),
    "userId" : 4,
    "movieId" : 2,
    "movie-categoryId" : 1,
    "reviewId" : 6,
    "movieRating" : 5,
    "reviewDate" : "7/15/2000"
}
{
    "_id" : ObjectId("57f4a0dd9cb74fc4d344a412"),
    "userId" : 4,
    "movieId" : 3,
    "movie-categoryId" : 1,
    "reviewId" : 2,
    "movieRating" : 5,
    "reviewDate" : "7/12/2000"
}
...

那么在 MapReduce 之后预期的输出 json 是:

{
    "_id" : ....,
    "userId" : 4,
    "movieList" : [ {
           "movieId" : 2
           "movieRating" : 5
         },
         {
           "movieId" : 1
           "movieRating" : 4
         }
         ...
        ]
   }
   {
    "_id" : ....,
    "userId" : 5,
    "movieList" : ...
   }
   ...

【问题讨论】:

  • 您能否更新您的问题以包含一些示例文档和您的预期输出?我很确定聚合框架可以更好、更有效地处理这个问题。
  • @chridam 检查编辑!
  • 我的意思是集合中的文档,即当您执行查询 db.ratings.find() 时,可能会选择 5 个文档来制作样本,并向我们展示您对样本聚合操作的预期 JSON 输出。否则,尝试使用上述信息重现问题是徒劳的。您能否使用示例文档和预期的 JSON 输出更新您的问题?
  • @chridam 检查编辑 2!
  • 嘿@chridam!感谢你的回答。您还可以帮助我指出如何使用 map reduce 函数来做到这一点吗?只是为了练习。

标签: mongodb hadoop mapreduce nosql


【解决方案1】:

您只需要运行一个聚合管道,该管道由一个汇总文档的 $group 阶段组成。这将按指定的标识符表达式对输入文档进行分组并应用累加器表达式。 $group 管道运算符类似于 SQL 的 GROUP BY 子句。在 SQL 中,您不能使用GROUP BY,除非您使用任何聚合函数。同样,您也必须在 MongoDB 中使用聚合函数。您可以在此处阅读有关聚合函数的更多信息。

创建movieList 数组所需的累加器运算符是$push。

$group 阶段之后的另一个管道是 $project 运算符,用于选择或重塑流中的每个文档,包括、排除或重命名字段,注入计算字段,创建子文档字段,使用数学表达式、日期、字符串和/或逻辑(比较、布尔、控制)表达式 - 类似于您对 SQL SELECT 子句所做的事情。

最后一步是 $out 管道,它将聚合管道的结果文档写入集合。它必须是管道中的最后一个阶段。

因此,您可以运行以下聚合操作:

db.ratings.aggregate([
    {
        "$group": {
            "_id": "$userId",
            "movieList": {
                "$push": {
                    "movieId": "$movieId",
                    "movieRating": "$movieRating",
                }
            }
        }
    },
    {
        "$project": {
            "_id": 0, "userId": "$_id", "movieList": 1
        }
    },
    { "$out": "movie_ratings_out" }
])

使用上面的示例 5 文档,如果您查询 db.getCollection('movie_ratings_out').find({}),示例输出将产生:

/* 1 */
{
    "_id" : ObjectId("57f52636b9c3ea346ab1d399"),
    "movieList" : [ 
        {
            "movieId" : 1.0,
            "movieRating" : 4.0
        }
    ],
    "userId" : 5.0
}

/* 2 */
{
    "_id" : ObjectId("57f52636b9c3ea346ab1d39a"),
    "movieList" : [ 
        {
            "movieId" : 1.0,
            "movieRating" : 4.0
        }, 
        {
            "movieId" : 2.0,
            "movieRating" : 5.0
        }, 
        {
            "movieId" : 3.0,
            "movieRating" : 5.0
        }
    ],
    "userId" : 4.0
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-25
    • 2014-08-01
    • 2019-11-24
    • 2018-08-11
    相关资源
    最近更新 更多