【问题标题】:Mongo Map-Reduce To Mimic "count(distinct(...)) group" by in SQLMongo Map-Reduce To Mimic "count(distinct(...)) group" by in SQL
【发布时间】:2014-12-17 17:45:13
【问题描述】:

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

{
    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 上试过这个,只是为了确保它不是版本问题。

【问题讨论】:

    标签: mongodb


    【解决方案1】:

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

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

    • Match 日期大于等于 指定日期。

    • Group 基于brand_id 字段。

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

    • Projectcount 每个键中的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 的输入值之一 该键的函数调用。

    你需要对你的map,reduce函数进行一些修改并添加一个新的finalize函数:

    • 您需要记住,当mongodb 调用reduce 多次对同一个键执行函数,前一个的结果 调用作为输入传递给 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;
            }
        }
    )
    

    【讨论】:

    • 在管道的 $project 步骤中尝试使用 $size 聚合函数需要 Mongo 2.6
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-11-07
    • 1970-01-01
    • 2020-01-03
    • 1970-01-01
    • 2012-07-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多