鉴于此文件格式
{
"_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'}}});
以下是结果
我希望我的每个文档都符合我指定的结果对象,但似乎并非如此。有人可以向我解释为什么我会看到这种行为吗?
答案 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" }
]);