我有以下文档结构:
[
{
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)])
答案 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会返回一个光标,因此您需要循环光标并打印结果。