计算嵌入文档/数组中字段的平均值
Posted
技术标签:
【中文标题】计算嵌入文档/数组中字段的平均值【英文标题】:Calculate the average of fields in embedded documents/array 【发布时间】:2015-07-29 02:57:50 【问题描述】:我想用数组 rating 中的 rating 字段来计算这个对象的 rating_average 字段。您能帮我了解如何使用 $avg 进行聚合吗?
"title": "The Hobbit",
"rating_average": "???",
"ratings": [
"title": "best book ever",
"rating": 5
,
"title": "good book",
"rating": 3.5
]
【问题讨论】:
【参考方案1】:MongoDB 3.4 和更新版本中的aggregation framework 提供了 $reduce
运算符,它可以有效地计算总数,而无需额外的管道。考虑将其用作表达式以返回
总评分并使用 $size
获取评分数。与 $addFields
一起,可以使用算术运算符 $divide
计算平均值,如公式 average = total ratings/number of ratings
:
db.collection.aggregate([
"$addFields":
"rating_average":
"$divide": [
// expression returns total
"$reduce":
"input": "$ratings",
"initialValue": 0,
"in": "$add": ["$$value", "$$this.rating"]
,
// expression returns ratings count
"$cond": [
"$ne": [ "$size": "$ratings" , 0 ] ,
"$size": "$ratings" ,
1
]
]
])
样本输出
"_id" : ObjectId("58ab48556da32ab5198623f4"),
"title" : "The Hobbit",
"ratings" : [
"title" : "best book ever",
"rating" : 5.0
,
"title" : "good book",
"rating" : 3.5
],
"rating_average" : 4.25
对于旧版本,您需要首先在 ratings
数组字段上应用 $unwind
运算符作为初始聚合管道步骤。这将从输入文档中解构 ratings
数组字段以输出每个元素的文档。每个输出文档都将数组替换为一个元素值。
第二个管道阶段是$group
运算符,它按_id
和title
键标识符表达式对输入文档进行分组,并将所需的$avg
累加器表达式应用于计算平均值的每个组。还有另一个累加器运算符$push
,它通过返回将表达式应用于上述组中的每个文档而产生的所有值的数组来保留原始评级数组字段。
最后的管道步骤是 $project
运算符,然后它会重塑流中的每个文档,例如通过添加新字段 ratings_average
。
因此,例如,如果您的收藏中有一个示例文档(如上等):
db.collection.insert(
"title": "The Hobbit",
"ratings": [
"title": "best book ever",
"rating": 5
,
"title": "good book",
"rating": 3.5
]
)
要计算评级数组的平均值并将值投影到另一个字段ratings_average
,您可以应用以下聚合管道:
db.collection.aggregate([
"$unwind": "$ratings"
,
"$group":
"_id":
"_id": "$_id",
"title": "$title"
,
"ratings":
"$push": "$ratings"
,
"ratings_average":
"$avg": "$ratings.rating"
,
"$project":
"_id": 0,
"title": "$_id.title",
"ratings_average": 1,
"ratings": 1
])
结果:
/* 1 */
"result" : [
"ratings" : [
"title" : "best book ever",
"rating" : 5
,
"title" : "good book",
"rating" : 3.5
],
"ratings_average" : 4.25,
"title" : "The Hobbit"
],
"ok" : 1
【讨论】:
【参考方案2】:这真的可以写得更短,甚至在撰写本文时也是如此。如果你想要一个“平均”,只需使用$avg
:
db.collection.aggregate([
"$addFields":
"rating_average": "$avg": "$ratings.rating"
])
原因是从 MongoDB 3.2 开始,$avg
运算符获得了“两个”东西:
能够以“表达式”形式处理参数“数组”,而不仅仅是作为$group
的累加器
受益于 MongoDB 3.2 允许数组表达式的“速记”表示法的特性。在作曲中:
"array": [ "$fielda", "$fieldb" ]
或将数组中的单个属性标记为该属性值的数组:
"$avg": "$ratings.rating" // equal to "$avg": [ 5, 3.5 ]
在早期版本中,您必须使用$map
才能访问每个数组元素内的"rating"
属性。现在你不知道了。
为了记录,即使是$reduce
的用法也可以简化:
db.collection.aggregate([
"$addFields":
"rating_average":
"$reduce":
"input": "$ratings",
"initialValue": 0,
"in":
"$add": [
"$$value",
"$divide": [
"$$this.rating",
"$size": "$ifNull": [ "$ratings", [] ]
]
]
])
是的,如上所述,这实际上只是重新实现了现有的$avg
功能,因此既然该运算符可用,那么它就是应该使用的。
【讨论】:
【参考方案3】:由于您将要计算的平均数据保存在一个数组中,因此您首先需要展开它。通过在聚合管道中使用 $unwind
来实现:
$unwind: "$ratings"
然后,您可以在聚合的结果文档中将数组的每个元素作为嵌入文档访问,键为 ratings
。然后你只需要$group
by title
并计算$avg
:
$group: _id: "$title", ratings: $push: "$ratings", average: $avg: "$ratings.rating"
然后恢复您的 title
字段:
$project: _id: 0, title: "$_id", ratings: 1, average: 1
这是您的结果聚合管道:
db.yourCollection.aggregate([
$unwind: "$ratings",
$group: _id: "$title",
ratings: $push: "$ratings",
average: $avg: "$ratings.rating"
,
$project: _id: 0, title: "$_id", ratings: 1, average: 1
])
【讨论】:
以上是关于计算嵌入文档/数组中字段的平均值的主要内容,如果未能解决你的问题,请参考以下文章