MongoDB Map减少给出混合结果

时间:2015-09-08 14:04:10

标签: mongodb mapreduce mongodb-query

鉴于此文件格式

{
    "_id" : ObjectId("55e99afda8deab702bb51001"),   
    "shippingStatus" : "",   
    "skuOwner" : ObjectId("55e99afd670a4c5b16e2a6ec")    
}

这是我正在尝试运行的地图缩小

inventory_map = function() {
    var values = {
        inventory: this._id,       
        count: 1
    };
    emit(this.skuOwner, values);
};

reduce = function(key, values) {
  var result = {      
      "openCount": 0,
      "inventory": []     
    };

    values.forEach(function(value) {
      result.openCount += 1;
      if(value.inventory !== null) {result.inventory.push(value.inventory)}
    });

    return result;
}


res = db.inventories.mapReduce(inventory_map, reduce, {out: 'openInventory', query: {shippingStatus: {$ne: 'SHIPPED'}}});

以下是结果

enter image description here

我希望我的每个文档都符合我指定的结果对象,但似乎并非如此。有人可以向我解释为什么我会看到这种行为吗?

2 个答案:

答案 0 :(得分:2)

相同的旧基本问题,但很难将这些标记为“重复”,因为所有实现实际上都是不同的,但问题的“相同”原因始终如此。

无论如何,你在这里使用了错误的方法,但请继续阅读以了解如何正确使用。

mapReduce阅读时,你基本上错过了这条至关重要的信息:

  

MongoDB可以为同一个密钥多次调用reduce函数。在这种情况下,该键的reduce函数的先前输出将成为该键的下一个reduce函数调用的输入值之一。

还有以后:

  

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

这意味着什么,你在这里基本上做错了是你的“映射器”正在返回完全不同的数据到你的“减速器”自己发出的数据。问题是因为reducer可以将“reduce函数”的前一个输出作为输入本身,基本上“再次减少”然后这就是一切都失败的地方。

为了澄清,“减少”不是“全有或全无”,而是一种“增量”方法,其中没有所有公共密钥值被呈现给功能“一下子”。而是仅呈现值的小“子集”,并且返回的输出可以再次“馈入降低”。这基本上是你处理“大数据”结果的方式,通过“块”而不是一次性处理。

解决这个问题通常就像使“mapper”产生与“reducer”期望的“input”相同的“输出”并且本身会产生“输出”一样简单。如此简单的改变在这里有所不同:

inventory_map = function() {
    var values = {
        inventory: [this._id],       
        openCount: 1                // all we changed on both
    };
    emit(this.skuOwner, values);
};

reduce = function(key, values) {
  var result = {      
      "openCount": 0,
      "inventory": []     
    };

    values.forEach(function(value) {
      result.openCount += value.openCount;           // and that too
      result.inventory = result.inventory.concat(value.inventory);      // that as well i guess
    });

    return result;
}

现在“mapper”和“reducer”的“输出”都是一样的,“reducer”也和“input”一样,所以它可以工作。

这方面的另一方面是,你似乎“应该”使用.aggregate()。由于操作非常简单,并且比mapReduce工作“快得多”,因为运算符都是本机编码的,不使用JavaScript解释:

db.inventories.aggregate([
    { "$group": {
        "_id": "$skuOwner",
        "inventory": { "$push": "$_id" },
        "count": { "$sum": 1 }
    }}
])

更简单,很多更快,也基本上简洁。好好学习。

答案 1 :(得分:-1)

MapReduce的一个重要要求是map-function的输出格式和reduce-function的输出格式是相同的。在您的代码中不是这种情况。您的地图输出格式为:

{
    inventory: this._id,       
    count: 1
};

并且您的reduce输出格式为:

{      
    openCount: 0,
    inventory: []     
};

这些格式必须相同的原因是因为当map提供的密钥只有一个值时,该结果可能根本不会传递给reduce并直接传递给输出。此外,reduce的任何结果都可能被放入另一轮reduce中,其中包含以前未经处理的结果(这通常仅在处理非常大的数据集或处理来自多个分片的数据时发生)。

那些仍然具有count字段并且inventory仍然是单个值而不是数组的结果从未传递给您的reduce函数。

要解决此问题,请修改map函数以返回与reduce函数输出相同的结果:

inventory_map = function() {
    var value = {
        inventory: [ this._id ],       
        openCount: 1
    };
    emit(this.skuOwner, value);
};

并相应地修改你的reduce函数:

reduce = function(key, values) {
  var result = {      
      "openCount": 0,
      "inventory": []     
    };

    values.forEach(function(value) {
      result.openCount += value.openCount;  // <--!!!
      if(value.inventory !== null) {
         result.inventory = result.inventory.concat(value.inventory); // <--!!!
      }
    });

    return result;
}

顺便提一下:解决问题的简单方法可能是aggregation

db.inventories.aggregate([
    { $match: {
        shippingStatus: {$ne: 'SHIPPED'}
    }},
    { $group: {
       _id: "$skuOwner",
       openCount: { $sum:1 }
    }},
    { $out: "openInventory" }
]);