Mongo Map-Reduce To Mimic" count(distinct(...))group"在SQL中

时间:2014-12-17 17:45:13

标签: mongodb

我想知道在一段时间内每个品牌销售了多少独特产品。单个文档如下所示:

{
    brand_id: 1,
    product_id: 2,
    date: ISODate("2014-12-12")
}

在SQL中,这将是:SELECT brand_id, count(distinct(product_id)) FROM orders WHERE date ... GROUP BY brand_id;

我无法通过Mongo的聚合框架(组等)来实现这一目标。这是我现在的map-reduce:

db.orders.mapReduce(
    function() {
        emit(this.brand_id, this.product_id);
    },
    function(key, values) {
        return values.filter(function (value, index, self) {return self.indexOf(value) === index;}).length;
    },
    {
        query: {date: {$gte: new Date('2014-11-20')}},
        out: "example"
    }
)

这看起来很不错。但是,我遇到的问题是reduce函数没有立即接收所有“值”,而是批量接收101个元素。因此,任何查找唯一值的尝试都会失败,而我得到的只是调用reduce函数的最后时间内唯一元素的数量。我无法想象如何在这里使用“finalize”属性来获得我想要的东西。

非常感谢任何想法。

我在Mongo 2.4和2.6上试过这个,只是为了确保它不是版本问题。

1 个答案:

答案 0 :(得分:2)

  

我无法想办法通过Mongo的聚合框架(组等)

您可以轻松汇总结果,而不是选择map-reduce解决方案:

  • Match日期大于等于的记录 指定日期。

  • Group基于brand_id字段。

  • 使用$addToSet运算符维护products唯一列表 每组product_id

  • Project每个密钥中count数组的products

代码:

db.collection.aggregate([
{$match:{"date":{$gte:new Date('2014-11-20')}}},
{$group:{"_id":"$brand_id","products":{$addToSet:"$product_id"}}},
{$project:{"_id":0,"brand_id":"$_id","distinct_prod":{$size:"$products"}}}
])

来到你的map-reduce解决方案,

  

但是,我遇到的问题是reduce函数没有立即接收所有“值”,而是批量接收101   元素

这是mongodb可以为每个组调用reduce函数的一种方式。来自docs

  

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

您需要对mapreduce个函数进行一些修改并添加新的finalize函数:

  • mongodb调用reduce时,您需要记住 函数对于同一个键不止一次,结果是前一个 调用作为reduce函数的输入传递,同时传递 下次调用reduce函数时的其他值。
  • 第一点,所以你需要确保减少输入 函数和reduce函数的返回值类似 构造,以便在reduce函数内写入的逻辑可以 容纳在之前的调用中处理自己的返回值。
  • 因为我们无法检索不同值的计数 当批量调用时,我们可以做的是,写一个reduce函数 为每个键累积不同的product_ids并写一个 finalize函数,用于计算这些唯一值的计数。

代码:

db.collection.mapReduce(
    function() {
        // emitting the same structure returned by the reduce function.
        emit(this.brand_id, {"prod_id":[this.product_id]});
    },
    function(key, values) {
       // the return value would be a list of unique product_ids.
        var res = {"prod_id":[]};
        for(var i=0;i<values.length;i++)
        {
         for(var j=0;j<values[i].prod_id.length;j++){
            if(res.prod_id.indexOf(values[i].prod_id[j]) == -1){
                res.prod_id.push(values[i].prod_id[j]);
            }
        }}
        return res;
    },
    {
        query: {date: {$gte: new Date('2014-11-20')}},
        out: "example",
        finalize: function(key, reducedValue){
            // it returns just the count
            return reducedValue.prod_id.length;
        }
    }
)