通过mongodb的加权平均评级

时间:2015-11-12 07:40:48

标签: mongodb mapreduce mongodb-query aggregation-framework

是否可以通过“加权平均值”进行排序

可能有1-5个值。加权平均值

(n5 * 5 + n4 * 4 + n3 * 3 + n2 * 2 + n1 * 1)/(n5 + n4 + n3 + n2 + n1)

其中n5是具有等级的对象的数量:5

我有以下示例。如果你找到更好的存储结构,我很高兴听到。

{
    "_id" : "wPg4jzJsEFXNxR5Wf",
    "caveId" : "56424a93819e7419112c883e",
    "data" : [
        {
            "value" : 1
        },
        {
            "value" : 3
        },
        {
            "value" : 4
        },
        {
            "value" : 2
        }
    ]
}
{
    "_id" : "oSrtv33MgnkJFvNan",
    "caveId" : "56424a93819e7419112c949f",
    "data" : [
        {
            "value" : 1
        },
        {
            "value" : 4
        },
        {
            "value" : 4
        },
        {
            "value" : 2
        }
    ]
}
{
    "_id" : "gJRMMQPwDwjFrL7zz",
    "caveId" : "56424a93819e7419112c8727",
    "data" : [
        {
            "value" : 5
        },
        {
            "value" : 1
        },
        {
            "value" : 4
        }
    ]
}

_ID的示例:oSrtv33MgnkJFvNan(第二个)

(2 * 4 + 1 * 2 + 1 * 1)/(2 + 1 + 1)= 2.75

然后我想按该值对所有文档进行排序。

订单将是

  1. gJRMMQPwDwjFrL7zz:value:3.33
  2. oSrtv33MgnkJFvNan:value 2.75
  3. wPg4jzJsEFXNxR5Wf:value 2.5

1 个答案:

答案 0 :(得分:2)

对于MongoDB可以从这样的计算中对数据进行排序,答案真的是“是”和“否”。它当然可以做到,但可能不符合您的实际目的。

MongoDB必须进行任何计算的两个工具是a ggregation frameworkmapReduce。前者目前缺乏操作者以实际方式真正处理这个问题。第二个可以被“欺骗”到排序中,作为mapReduce如何工作的工件,通过将要排序的组件放在分组键中(即使没有实际的分组)。

所以你基本上可以用这样的东西来应用数学:

db.data.mapReduce(
    function() {
        var vals = this.data.map(function(el){ return el.value }),
            uniq = {};

        vals.forEach(function(el) {
            if (!uniq.hasOwnProperty(el)) {
                uniq[el] = 1;
            } else {
                uniq[el]++;
            }
        });

        var weight = Array.sum(Object.keys(uniq).map(function(key) {
            return uniq[key] * key
        })) / Array.sum(Object.keys(uniq).map(function(key) {
            return uniq[key];
        }))

        var id = this._id;
        delete this._id;

        emit({ "weight": weight, "orig": id },this);

    },
    function() {},
    { "out": { "inline": 1 } }
)

这为你提供了这个输出:

{
    "results" : [
            {
                    "_id" : {
                            "weight" : 2.5,
                            "orig" : "wPg4jzJsEFXNxR5Wf"
                    },
                    "value" : {
                            "caveId" : "56424a93819e7419112c883e",
                            "data" : [
                                    {
                                            "value" : 1
                                    },
                                    {
                                            "value" : 3
                                    },
                                    {
                                            "value" : 4
                                    },
                                    {
                                            "value" : 2
                                    }
                            ]
                    }
            },
            {
                    "_id" : {
                            "weight" : 2.75,
                            "orig" : "oSrtv33MgnkJFvNan"
                    },
                    "value" : {
                            "caveId" : "56424a93819e7419112c949f",
                            "data" : [
                                    {
                                            "value" : 1
                                    },
                                    {
                                            "value" : 4
                                    },
                                    {
                                            "value" : 4
                                    },
                                    {
                                            "value" : 2
                                    }
                            ]
                    }
            },
            {
                    "_id" : {
                            "weight" : 3.3333333333333335,
                            "orig" : "gJRMMQPwDwjFrL7zz"
                    },
                    "value" : {
                            "caveId" : "56424a93819e7419112c8727",
                            "data" : [
                                    {
                                            "value" : 5
                                    },
                                    {
                                            "value" : 1
                                    },
                                    {
                                            "value" : 4
                                    }
                            ]
                    }
            }
    ]
}

因此所有结果都已排序,但当然限制条件是mapReduce只能生成16MB BSON限制以下的“内联”输出,或者将结果写入另一个集合。

即使将新功能添加到聚合框架中可以提供帮助(来自当前的开发系列3.1.x),这仍然需要与$unwind进行一些杂乱,以便获得任何元素的“总和”方式(还没有“减少”功能这样的功能),这并不能使它成为一种稳定或实用的替代方案。

所以你可以用mapReduce来做,但是为了我的钱,我会有另一个进程来计算它以定期运行(或在更新时触发)并更新文档上的标准“权重”字段,然后可以直接使用用于分类。

在文档中设置一个值始终是最高性能的选项。

对于好奇的人,您可以获取MongoDB(3.1.x系列)的开发分支版本或之后的任何版本并应用这样的聚合管道:

db.data.aggregate([
    {  "$project": {
        "caveId": 1,
        "data": 1,
        "conv": {
            "$setUnion": [
                { "$map": {
                    "input": "$data",
                    "as": "el",
                    "in": "$$el.value"
                }},
                []
            ]
        },
        "orig": { 
            "$map": {
                "input": "$data",
                "as": "el",
                "in": "$$el.value"
            }
        }
    }},
    { "$project": {
        "caveId": 1,
        "data": 1,
        "conv": 1,
        "orig": 1,
        "counts": { "$map": {
            "input": "$conv",
            "as": "el",
            "in": {
                "$size": {
                    "$filter": {
                        "input": "$orig",
                        "as": "o",
                        "cond": { 
                            "$eq": [ "$$o", "$$el" ]
                        }
                    }
                }
            }
        }}
    }},
    { "$unwind": { "path": "$conv", "includeArrayIndex": true } },
    { "$group": {
        "_id": "$_id",
        "caveId": { "$first": "$caveId" },
        "data": { "$first": "$data" },
        "counts": { "$first": "$counts" },
        "mult": { 
            "$sum": { 
                "$multiply": [ 
                    "$conv.value", 
                    { "$arrayElemAt": [ "$counts", "$conv.index" ] }
                ]
            }
        }
    }},
    { "$unwind": "$counts" },
    { "$group": {
        "_id": "$_id",
        "caveId": { "$first": "$caveId" },
        "data": { "$first": "$data" },
        "count": { "$sum": "$counts" },
        "mult": { "$first": "$mult" }
    }},
    { "$project": {
        "data": 1,
        "weight": { "$divide": [ "$mult", "$count" ] }
    }},
    { "$sort": { "weight": 1 } }
])

但即使$filter中的$unwind和“includeArrayIndex”等帮助程序以及稍后使用该索引的$arrayElemAt运算符来匹配不同的元素及其计数,{{ 1}}以任何方式使这成为一种不具备性能的解决方案。

如果像$unwind这样的运算符可以生成配对所需的索引值,并且引入任何方法来类似地对数组结果执行“内联求和”操作或其他数学运算,那么将来可能会变得切实可行处理$map。但是在编写时,即使在开发过程中也不存在。