使用 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 聚合计算计数和平均值的主要内容,如果未能解决你的问题,请参考以下文章