为什么当给定密钥下有单个文档时,此映射会减少查询失败?

时间:2015-12-21 22:46:12

标签: mongodb mapreduce aggregation-framework

我有一个名为inbound_pos的集合,其文档中有一个名为lines的密钥。一个例子如下:

{
   "_id": ObjectId("537e16cf8be10a5e7e000008"),
   "cdt": ISODate("2014-05-22T15:25:03.379Z"),
   "comments": [
     [

    ] 
  ],
   "cust_shipto_id": "103",
   "doc_dt": "20140522",
   "isa_control_id": "000030456",
   "kind": "edi",
   "lines": [
     {
       "linenumber": "1",
       "net_qty": NumberInt(10),
       "uom": "EA",
       "unitcost": 10.04,
       "v_nmbr": "005-2964",
       "tm_desc": "NA" 
    },
     {
       "linenumber": "2",
       "net_qty": NumberInt(10),
       "uom": "EA",
       "unitcost": 13.59,
       "v_nmbr": "005-2966",
       "tm_desc": "NA" 
    },
     {
       "linenumber": "3",
       "net_qty": NumberInt(6),
       "uom": "BX",
       "unitcost": 18.36,
       "v_nmbr": "2201254",
       "tm_desc": "LANTISEPTIC" 
    } 
  ]
}

在构成lines数组的每个对象中,v_nmbrnet_qtyunitcost键表示正在购买的特定商品,购买的数量为该项目,以及该项目的单价。

我要做的是获取集合中所有文档的每v_nmbr个花费的总金额。 map-reduce查询如下:

db.inbound_pos.mapReduce(function() {
    for (var i=0; i<this.lines.length; i++) {
      emit(this.lines[i].v_nmbr, this.lines[i])
    }
}, function(key, values) {
    var totalExpenses = 0;
    for (var i=0; i<values.length; i++) {
      totalExpenses += values[i].unitcost * values[i].net_qty
    }
    return totalExpenses
}, {
    out: "total_expenses_per_item"
})

此查询会为已购买多次的所有商品正确生成总金额,但对于仅购买过一次的商品则无效。

以下是一些示例输出:

成功 (项目购买了20次)

{
   "_id": "005-BUHW2076HRF",
   "value": 2366.4 
}

(项目被购买2次):

{
   "_id": "P54072",
   "value": 29.13 
}

失败(商品只购买一次):

{
   "_id": "OTC11780",
   "value": {
     "linenumber": "1",
     "v_nmbr": "OTC11780",
     "net_qty": NumberInt(5),
     "unitcost": 13.68,
     "uom": "BT",
     "tm_desc": "VITAMIN E 1000 IU SOFTGEL 100/BTL" 
  } 
}

如果您有时间,我们将非常感谢您的帮助。

2 个答案:

答案 0 :(得分:1)

From the documentation:MongoDB不会为只有一个值的键调用reduce函数。它只返回映射值作为结果。

但是MongoDB无论如何都会调用单个映射值的finalize函数,所以你可以在那里修改结果。

最佳解决方案是遵循以下规则:reduce函数的返回对象的类型必须与map函数发出的值的类型相同。

在你的情况下,我会以这种方式做map-reduce:

db.inbound_pos.mapReduce(function() {
    for (var i=0; i<this.lines.length; i++) {
      emit(this.lines[i].v_nmbr, this.lines[i].unitcost * this.lines[i].net_qty)
    }
}, function(key, values) {
    var totalExpenses = 0;
    for (var i=0; i<values.length; i++) {
      totalExpenses += values[i]
    }
    return totalExpenses
}, {
    out: "total_expenses_per_item"
})

答案 1 :(得分:1)

试试这个:

db.inbound_pos.mapReduce(function() {
    for (var i=0; i<this.lines.length; i++) {
        emit(this.lines[i].v_nmbr, this.lines[i])
    }
}, function(key, values) {
    var totalExpenses = 0;
    for (var i=0; i<values.length; i++) {
        totalExpenses += values[i].unitcost * values[i].net_qty
    }
    return totalExpenses
}, {
    finalize: function(key,reducedValue) {
        if (typeof reducedValue.linenumber != 'undefined') {
            return reducedValue.unitcost;
        } else {
            return reducedValue;
        }
    },
    out: "total_expenses_per_item"
});

如果只有一个值,则map reduce不会进入reduce阶段,在这种情况下,你可以使用finalize函数只返回你想要的值。