mongodb - 通过 mongodb 加权平均评分

标签 mongodb mapreduce mongodb-query aggregation-framework

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

有 5 个可能的值,范围为 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:值:3.33
  2. oSrtv33MgnkJFvNan:值 2.75
  3. wPg4jzJsEFXNxR5Wf:值 2.5

最佳答案

关于 MongoDB 是否可以像这样通过计算对数据进行排序,答案实际上是"is"和“否”。它当然可以做到这一点,但可能无法以实际的方式实现您的目的。

MongoDB 必须执行任何类型计算的两个工具是 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 运算符不同的元素及其计数,以任何方式使用 $unwind 都会导致该解决方案性能不佳。

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

关于mongodb - 通过 mongodb 加权平均评分,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33666418/

相关文章:

python-2.7 - 无法在 python 中使用 mapreduce

php - 使用映射和聚合将 Mongodb shell 查询转换为 php

java - 过滤 mongodb 查询后返回的数组元素

python - 如何加入4个mongo集合

C# Mongo DeleteMany - 不使用类

mongodb 副本集和 ec2 - 主机名是什么

Java:从文本中读取字符和字节

performance - 分布式局部聚类系数算法(MapReduce/Hadoop)

mongodb - 查找操作后隐藏嵌套文档中的 _id

r - 使用哪个包将 R 与 MongoDB 连接起来?