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

时间:2015-05-17 16:13:21

标签: mongodb mongodb-query average aggregation-framework

我想用数组评级中的评级字段计算此对象的rating_average字段。你能帮我理解如何使用$ avg聚合?

{
    "title": "The Hobbit",
    "rating_average": "???",
    "ratings": [
        {
            "title": "best book ever",
            "rating": 5
        },
        {
            "title": "good book",
            "rating": 3.5
        }
    ]
}

3 个答案:

答案 0 :(得分:11)

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
}

答案 1 :(得分:4)

这真的可以写得那么短,在撰写本文时甚至都是如此。如果您想要“平均”,只需使用$avg

db.collection.aggregate([
  { "$addFields": {
    "rating_average": { "$avg": "$ratings.rating" }
  }}
])

原因是,从MongoDB 3.2开始,$avg运营商获得了“两件”的东西:

  1. 以“表达式”形式处理参数“数组”的能力,而不仅仅是$group的累加器

  2. MongoDB 3.2的功能带来的好处,它允许数组表达式的“速记”表示法。在组成中:

    { "array": [ "$fielda", "$fieldb" ] }
    

    或从数组中注释单个属性作为该属性值的数组:

    { "$avg": "$ratings.rating" } // equal to { "$avg": [ 5, 3.5 ] }
    
  3. 在早期版本中,您必须使用$map才能访问每个数组元素中的"rating"属性。现在你没有。

    为了记录,即使$reduce用法也可以简化:

    db.collection.aggregate([
      { "$addFields": {
        "rating_average": {
          "$reduce": {
            "input": "$ratings",
            "initialValue": 0,
            "in": {
              "$add": [ 
                "$$value",
                { "$divide": [ 
                  "$$this.rating", 
                  { "$size": { "$ifNull": [ "$ratings", [] ] } }
                ]}
              ]
            }
          }
        }
      }}
    ])
    

    如上所述,这实际上只是重新实现了现有的$avg功能,因此,由于该运营商可用,因此它应该是应该使用的。

答案 2 :(得分:2)

由于数组中有待计算的平均数据,首先需要将其展开。通过在聚合管道中使用$unwind来执行此操作:

{$unwind: "$ratings"}

然后,您可以在聚合的结果文档中将数组的每个元素作为嵌入文档访问,其中包含键ratings。然后,您需要$group 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}}
                            ])
相关问题