如何计算符合给定条件的子文档的平均值总和?

时间:2015-11-24 10:12:23

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

我有以下文档结构:

[
    {
        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
                    }
                ]
            }
        ]
    },
    ...
]

现在,我希望在给出一份评论家名单(最好订购)的情况下,找到每本书平均分数得分最高的系列。

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

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

或只是系列ID的有序列表:

[ 0, ... ]

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

现在我被困在:

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

1 个答案:

答案 0 :(得分:0)

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

为了在$project阶段之后$redact您的文档,并在我们的投影中使用$avg运算符,我们可以返回{{3}返回的分数数组的平均值然后使用$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之前,您需要对#34;书籍进行非规范化。数组然后$project我们的文件。然后我们可以使用$map返回减少将在下一阶段处理的文档的大小。然后是$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 }

另一种方法是首先过滤出批评者' crit_id'不是$redact [0, 2]使用$in运算符。在我们的管道中接下来是$match阶段,以对两个"书籍"进行非规范化。和"分数"阵列。从那里你需要两个$unwind阶段。第一个计算"得分的$group"第二个返回这些平均值的$avg

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 }

您始终可以在管道末尾添加一个可选的$sum阶段,如下所示:

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

要归还:

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

但这会导致性能下降。

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