使用 MongoDB 聚合计算计数和平均值

Posted

技术标签:

【中文标题】使用 MongoDB 聚合计算计数和平均值【英文标题】:Calculating count and average with MongoDB aggregation 【发布时间】:2012-10-11 00:32:04 【问题描述】:

我有一个像这样的简单数据库布局:

client
    id
    sex (male/female)
    birthday (date)    

client
    id
    sex (male/female)
    birthday (date)  

(...)

我正在尝试编写一个聚合命令,输出我有多少男性和女性客户,我还想输出男性和女性的平均年龄,不确定我是否可以这样做命令还是我需要 2 个单独的命令?

// Count of males/females, average age
Clients.aggregate(
    $project : "sex"      : 1,
            "sexCount" : 1,
            "birthday" : 1,
            "avgAge"   : 1
                
    ,
    
        $match: "sex": $exists: true 
    ,
    
        $group: 
                    _id      : "$sex",
            sexCount :  $sum: 1 ,
            avgAge   :  $avg: "$birthday" ,
            
    ,
     $sort:  _id: 1  
    , function(err, sex_dbres) 
            if (err)
                throw err;
            else
                (...)
            
        );         

通过上面的代码,我得到了男性/女性的计数,但 avgAge 为 0。有什么想法吗?

非常感谢

【问题讨论】:

请注意,您不需要在第一步中投影字段 sexCount 或 avgAge,因为这些字段是您将在 $group 步骤中计算的字段。 【参考方案1】:

日期对象不能“平均”,但数字可以。您可以将日期转换为时间戳值,然后从中找到平均值。但这仍然不是平均年龄,您需要从聚合函数之外的当前日期中减去结果。

另一种选择是假设可以仅使用日期的年份部分来计算年龄(也就是说,如果我出生于 2000 年 12 月 1 日,那么在今天的报告中,我将是 12 岁,而不是 11 岁)。在这种情况下,您可以使用date operators 提取年份值。

$project : "sex"      : 1,
            "sexCount" : 1,
            "year" : $year: "$birthday",
            
,
$project : "sex"      : 1,
            "sexCount" : 1,
            "age" : $subtract: [2012, '$year'],
            
,

【讨论】:

谢谢。刚刚意识到生日存储为字符串(“Sat May 22 1982 00:00:00 GMT+0200”),这让事情变得更加困难。是否可以将其转换为数字?我试过做一个 substr 来得到年份部分,但是我很难把它变成一个数字然后做你建议的 $subtract 。如果这很难,那么我想我可以把这个字段变成一个日期。 转换不是聚合框架的一部分,我猜你要么需要使用 MapReduce 来编写任意 javascript 代码,要么运行数据库并转换所有日期。 谢谢!我将年份提取到一个单独的字段中,现在可以轻松地求平均值。【参考方案2】:

如果您将年龄存储在原始文档中,答案会简单得多(正如 Dmitry 发布的那样,您可以在 $group 步骤中直接执行 avgAge:$avg:"$age"

聚合框架非常漂亮,它有许多很酷的运算符,可让您“即时”计算这个缺失的年龄字段。

我要将聚合的每个步骤存储在一个变量中,以便更容易看到发生了什么:

today = new Date();
// split today and bday into numerical year and numerical day-of-the-year
project1= 
    "$project" : 
        "sex" : 1,
        "todayYear" : 
            "$year" : today
        ,
        "todayDay" : 
            "$dayOfYear" : today
        ,
        "by" : 
            "$year" : "$bday"
        ,
        "bd" : 
            "$dayOfYear" : "$bday"
        
    
;
// calculate age in days by subtracting bday in days from today in days
project2 = 
    "$project" : 
        "sex" : 1,
        "age" : 
            "$subtract" : [
                
                    "$add" : [
                        
                            "$multiply" : [
                                "$todayYear",
                                365
                            ]
                        ,
                        "$todayDay"
                    ]
                ,
                
                    "$add" : [
                        
                            "$multiply" : [
                                "$by",
                                365
                            ]
                        ,
                        "$bd"
                    ]
                
            ]
        
    
;
// sum up for each sex the count and compute avg age (in days)
group = 
    "$group" : 
        "_id" : "$sex",
        "total" : 
            "$sum" : 1
        ,
        "avgAge" : 
            "$avg" : "$age"
        
    
;
// divide days by 365 to get age in years.
project3 = 
    "$project" : 
        "_id" : 0,
        "sex" : "$_id",
        "total" : 1,
        "averageAge" : 
            "$divide" : [
                "$avgAge",
                365
            ]
        
    
;

现在您可以运行聚合了:

> db.client.find(,_id:0)
 "sex" : "male", "bday" : ISODate("2000-02-02T08:00:00Z") 
 "sex" : "male", "bday" : ISODate("1987-02-02T08:00:00Z") 
 "sex" : "female", "bday" : ISODate("1989-02-02T08:00:00Z") 
 "sex" : "female", "bday" : ISODate("1993-11-02T08:00:00Z") 
> db.client.aggregate([ project1, project2, group, project3 ])

    "result" : [
        
            "sex" : "female",
            "total" : 2,
            "averageAge" : 21.34109589041096
        ,
        
            "sex" : "male",
            "total" : 2,
            "averageAge" : 19.215068493150685
        
    ],
    "ok" : 1

> 

这不简单的原因是目前聚合框架不支持直接减去日期。请投票给https://jira.mongodb.org/browse/SERVER-6239,它是下一个主要版本的目标——一旦实施,它应该允许直接减去日期(尽管您仍然需要将其转换为适当的粒度,在这种情况下可能是几年)。

【讨论】:

当然,另一种方法是将 bday 转换为天,获取组步骤中的平均值,并在最终项目中计算从今天开始的天数减去 bday 天数除以 365 . 感谢 Asya,方法与上述类似。

以上是关于使用 MongoDB 聚合计算计数和平均值的主要内容,如果未能解决你的问题,请参考以下文章

Mongodb的聚合和管道

MongoDB 聚合

MongoDB之聚合

MongoDB 聚合

MongoDB 聚合

1.表的聚合查询(和平均最大最小值)