【问题标题】:Weighted Average rating through mongodb通过 mongodb 的加权平均评级
【发布时间】:2016-02-13 11:32:20
【问题描述】:

是否可以进行查询以按“加权平均”排序

从 1 到 5 有 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:值:3.33
  2. oSrtv33MgnkJFvNan:值 2.75
  3. wPg4jzJsEFXNxR5Wf:值 2.5

【问题讨论】:

    标签: mongodb mapreduce mongodb-query aggregation-framework


    【解决方案1】:

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

    MongoDB 用来进行任何类型计算的两个工具是 aggregation 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 进行一些杂耍才能以任何方式获得元素的“总和”(还没有“reduce”功能这样的功能),这并不能使其成为稳定或实用的替代方案。

    所以你可以用 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 } }
    ])
    

    但即使在 $unwind 中使用 $filter 和 "includeArrayIndex" 之类的助手,以及稍后使用该索引将不同元素与它们的计数匹配的 $arrayElemAt 运算符,$unwind 的使用以任何方式都可以做到这一点一个无效的解决方案。

    如果像$map 这样的运算符可以生成配对所需的索引值,并且引入任何方法来类似地对数组结果进行“内联求和”运算或其他数学运算而不处理@,那么将来可能会变得实用987654332@。但在撰写本文时,即使在开发中,它也不存在。

    【讨论】:

    • 非常感谢您的大量撰写!我怀疑我会有很多评级,但也许有一个 cronjob 来进行计算是明智的。我将同时尝试它们并查看一些性能指标。
    • @Chris 这是一个明智的选择。我有一个共同的口头禅“测试,测试然后再测试”。这主要是指代码和方法的性能特征,并找出最适合情况的。如果事实证明它是最有效的,您总是可以“即时”计算。但实际上,选择应该始终是最有效的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-08
    • 2010-09-21
    • 1970-01-01
    • 2015-10-18
    • 1970-01-01
    相关资源
    最近更新 更多