计算嵌入文档/数组中字段的平均值

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 运算符,它按_idtitle 键标识符表达式对输入文档进行分组,并将所需的$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
                            ])

【讨论】:

以上是关于计算嵌入文档/数组中字段的平均值的主要内容,如果未能解决你的问题,请参考以下文章

单词嵌入的语义加权平均值

合并二维数组,并计算重复字段的平均值

AS3:从数组中获取平均值[重复]

Numpynumpy.mean() 的用法

Elasticsearch Avg聚合

Elasticsearch Avg聚合