【问题标题】:How to calculate the sum of average of subdocuments that match a given criteria?如何计算匹配给定条件的子文档的平均值总和?
【发布时间】:2015-11-24 10:12:22
【问题描述】:

我的文档结构如下:

[
    {
        series_id: 0,
        books: [
            {
                book_id: 0,
                scores: [
                    {
                        critic_id: 0,
                        score : 7.5
                    },
                    {
                        critic_id: 1,
                        score : 8.5
                    },
                    {
                        critic_id: 2,
                        score : 2.5
                    }
                ]
            },
            {
                book_id: 1,
                scores: [
                    {
                        critic_id: 0,
                        score : 5.5
                    },
                    {
                        critic_id: 1,
                        score : 7.5
                    },
                    {
                        critic_id: 2,
                        score : 9.5
                    }
                ]
            }
        ]
    },
    ...
]

现在,我想在给定评论家 ID 列表(最好是有序的)的情况下,找到每本书平均得分最高的系列。

因此,例如,我想找到评论家得分最高的系列 [0,2]。这应该返回:

[
    {
        series_id: 0,
        score: 12.5 
    },
    ...
]

或者只是一个序列号的有序列表:

[ 0, ... ]

因为评论家 0 和 2 的 book_0 的平均值是 5,而评论家 0 和 2 的 book_1 的平均值是 7.5。总和这是 12.5

现在我被困在:

return list(db['series']).find(sort=[("series.books.scores", 1)])

【问题讨论】:

    标签: python mongodb mongodb-query pymongo aggregation-framework


    【解决方案1】:

    从版本 3.2 开始,$avg 累加器表达式以前只能在 $group 阶段使用,现在也可以在 $project 阶段使用,然后我们可以利用它来缩短我们之前的管道。

    为此,您在$redact 阶段之后$project 您的文档并在我们的投影中使用$avg 运算符,我们可以返回$map 返回的分数数组的平均值,然后使用$group 到返回预期的结果。

    db.series.aggregate([
        { '$match': {
            'books.scores.critic_id': { '$in': [ 0,2 ] }
        }},
        { '$unwind': '$books' }, 
        { '$project': { 
            'series_id': 1, 
            'book_id': '$books.book_id', 
            'scores': '$books.scores'
        }},
        { '$redact': {
            '$cond': [
                { '$or': [
                    { '$eq': [ '$critic_id', 0 ] }, 
                    { '$eq': [ '$critic_id', 2 ] },
                    { '$not': '$critic_id' }
                ]}, 
                '$$DESCEND', '$$PRUNE'
            ]
        }}, 
        { '$project': { 
            'series_id': 1, 
            'score': {
                '$avg': { 
                    '$map': { 
                        'input': '$scores', 
                        'as': 'score', 
                        'in': '$$score.score'
                    }
                }
            }
        }}, 
        { '$group': { 
            '_id': '$_id', 
            'series_id': { '$first': '$series_id' }, 
            'score': { '$sum': '$score' }
        }}
    ])
    

    产量:

    { "_id" : ObjectId("56543c98571635184da33953"), "series_id" : 0, "score" : 12.5 }
    

    在 MongoDB 3.2 之前,您需要非规范化“books”数组,然后 $project 我们的文档。然后我们可以从那里使用$redact 返回减小将在下一阶段处理的文档的大小。然后是$unwind$group 阶段。

    db.series.aggregate([
        { '$match': { 
            'books.scores.critic_id': { '$in': [ 0, 2 ] }
        }},
        { '$unwind': '$books' }, 
        { '$project': { 
            'series_id': 1, 
            'book_id': '$books.book_id', 
            'scores': '$books.scores'
        }},
        { '$redact': {
            '$cond': [
                { '$or': [
                    { '$eq': [ '$critic_id', 0 ] }, 
                    { '$eq': [ '$critic_id', 2 ] }, 
                    { '$not': '$critic_id' } 
                ]}, 
                '$$DESCEND', '$$PRUNE'
            ]
        }}, 
        { '$unwind': '$scores' },
        { '$group': { 
            '_id': '$book_id', 
            'series_id': { '$first': '$series_id' }, 
            'avgScores': { '$avg': '$scores.score' }
        }},
        { '$group': {
            '_id': '$series_id', 
            'score': { '$sum': '$avgScores' }
        }} 
    ])
    

    产量:

    { "_id" : 0, "score" : 12.5 }
    

    另一种方法是首先使用 $match 运算符过滤掉“critic_id”而不是 $in [0, 2] 的文档。我们管道中的下一个是$unwind 阶段,用于对“books”和“scores”数组进行非规范化。从那里你需要两个$group 阶段。第一个计算“分数”的$avg,第二个返回这些平均值的$sum

    db.series.aggregate([
        { '$match': {
            'books.scores.critic_id': { '$in': [ 0,2 ] }
        }}, 
        { '$unwind': '$books' }, 
        { '$unwind': '$books.scores' },
        { '$match': { 
            'books.scores.critic_id': { '$in': [ 0, 2 ] }
        }}, 
        { '$group': { 
            '_id': '$books.book_id',
            'series_id': { '$first': '$series_id' }, 
            'total': { '$avg': '$books.scores.score' }
        }}, 
        { '$group': { 
            '_id': '$series_id', 
            'score': { '$sum': '$total' }
        }}
    ])
    

    返回:

    { "_id" : 0, "score" : 12.5 }
    

    您始终可以将可选的$project 阶段添加到管道的末尾,如下所示:

    { '$project': { 
        'series_id': '$_id', 
        'score': 1, 
        '_id': 0
    }} 
    

    返回这个:

    { "score" : 12.5, "series_id" : 0 }
    

    但这会导致性能下降。

    值得注意的是,PyMongo 返回一个游标,因此您需要循环游标并打印结果。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-09-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-13
      • 1970-01-01
      相关资源
      最近更新 更多