MongoDB中的MapReduce功能—按ID分组文档



我正在尝试学习MongoDB中的MapReduce功能。我不想使用聚合,而是想使用MapReduce函数根据我自己定义的键对集合中的文档进行分组。

My collection Cool is:

/* 1 */{"_id": ObjectId("55d5e7287e41390ea7e83a55"),id: a;"cool": "a1"}

/* 2 */{"_id": ObjectId("55d5e7287e41390ea7e83a56"),id: a;"cool": "a2"}

/* 3 */{"_id": ObjectId("55d5e7287e41390ea7e83a57"),id: b;"cool": "b1"}

/* 4 */{"_id": ObjectId("55d5e7287e41390ea7e83a58"),id: b;"cool": "b2"}

/* 5 */{"_id": ObjectId("55d5e7287e41390ea7e83a59"),id: c;"cool": "c1"}

/* 6 */{"_id": ObjectId("55d5e7287e41390ea7e83a5a"),id: d;"cool": "d1"}

下面是我的MapReduce函数:

db.Cool.mapReduce(
    function(){emit(this.id, this.cool)},
    function(key, values){
        var res = [];
        values.forEach(function(v){
            res.push(v);
            });
        return {cools: res};
        },
    {out: "MapReduce"}     
)

我想得到这样的结果:

/* 1 */{_id: a,"value": {"冷却":["a1","a2"]}}

但是在返回集合中,有:

/* 1 */{_id: a,"value": {"冷却":["a1","a2"]}}

/* 2 */{_id: b;"value": {"冷却":["b1","b2"]}}

/* 3 */{_id: c;"value": "c1"}

/* 4 */{_id: d,"value": "d1"}

问题是:为什么文档"id":"a"(有多个文档"id":"a")和文档"id":"c"(只有一个文档"id":"c")之间存在差异

谢谢你的建议,很抱歉我的英语不好。

在您的学习中,您可能错过了mapReduce的核心手册页。有一条至关重要的信息,你要么错过了,要么没有阅读和学习:

MongoDB可以对同一个键多次调用reduce函数。在本例中,该键的reduce函数先前的输出将成为该键的下一次reduce函数调用的输入值之一。

再后面一点:

返回对象的类型必须与map函数发出的值的类型相同。

所以这基本上意味着,因为"reducer"实际上并没有一次处理"所有"唯一键,所以它期望得到与它给出的"输出"相同的"输入",因为输出可以再次反馈到reducer。

出于同样的原因,"mapper"需要准确地输出预期的"reducer"输出,这也是reducer的"输入"。所以你实际上根本没有"改变"数据结构,而只是"减少"它。

db.Cool.mapReduce(
    function(){emit(this.id, { "cools": [this.cool] })},
    function(key, values){
        var res = [];
        values.forEach(function(cool){
            cool.cools.forEach(function(v) {
                res.push(v);
            });
        });
        return {cools: res};
    },
    {out: "MapReduce"}     
)

现在您正在将输入作为一个数组处理,该数组也是输出,然后返回预期的结果。

接下来要学习的是,在大多数情况下,mapReduce并不是你真正想要使用的,你应该使用聚合框架。

与mapReduce相反,它使用"本地编码"操作符,不需要JavaScript解释即可运行。这在很大程度上意味着它"更快",而且在构造上通常要简单得多。

下面是与.aggregate()相同的操作:

db.Cool.aggregate([
    { "$group": {
        "_id": "$id",
        "cools": { "$push": "$cool" }
    }}
])

同样的事情,更少的代码和更快。

输出到另一个集合你使用 $out :

db.Cool.aggregate([
    { "$group": {
        "_id": "$id",
        "cools": { "$push": "$cool" }
    }},
    { "$out": "reduced" }
])

作为记录,下面是mapReduce的输出:

{ "_id" : "a", "value" : { "cools" : [ "a1", "a2" ] } }
{ "_id" : "b", "value" : { "cools" : [ "b1", "b2" ] } }
{ "_id" : "c", "value" : { "cools" : [ "c1" ] } }
{ "_id" : "d", "value" : { "cools" : [ "d1" ] } }

和总产出。与mapReduce _idvalue强制输出的唯一区别是键是反向的,因为$group不保证顺序(但通常被观察为反向堆栈):

{ "_id" : "d", "cools" : [ "d1" ] }
{ "_id" : "c", "cools" : [ "c1" ] }
{ "_id" : "b", "cools" : [ "b1", "b2" ] }
{ "_id" : "a", "cools" : [ "a1", "a2" ] }

map函数和reduce函数的返回值需要相同。否则,集合中的单个值将按照您在map函数中指定的返回。这是由于一个优化,因为reduce函数不会对在map阶段返回单个值的键执行。你可以这样做:

db.Cool.mapReduce(
    function () {
        emit(this.id, {cools: [this.cool]}) // same data structure as  in your reduce function
    },
    function (key, values) {
        var res = {cools: []}; // same data structure as the value of map phase
        values.forEach(function (v) {
            res.cools = res.cools.concat(v.cools);
        });
        return res;
    },
    {out: "MapReduce"}
)

最新更新