mongodb作为常用的非关系型数据库,提供了丰富的统计数据的能力,开发者可以根据不同的统计需求选择对应的实现方式,从简单的文档数量统计到复杂的多维度分组聚合都能支持。
mongodb基础统计方法:count
如果只是需要统计集合中符合某个条件的文档总数,使用count方法是最简单高效的选择。这个方法可以直接返回匹配的文档数量,不需要处理复杂的聚合逻辑。
count方法有两种使用形式,一种是在集合上直接调用,另一种是在find查询之后调用,两者的效果一致。
// 统计整个集合的文档总数
db.user.count()
// 统计年龄大于20的用户数量
db.user.count({ age: { $gt: 20 } })
// 也可以先执行find再count,效果和上面一致
db.user.find({ age: { $gt: 20 } }).count()
需要注意的是,在mongodb 4.0之后,官方更推荐使用countDocuments和estimatedDocumentCount替代原来的count方法,这两个方法的统计逻辑更清晰,countDocuments会准确统计匹配条件的文档数,estimatedDocumentCount会基于集合的元数据快速返回总数,性能更高但结果可能不是实时准确的。
// 准确统计年龄大于20的用户数量
db.user.countDocuments({ age: { $gt: 20 } })
// 快速统计整个集合的文档总数,不需要传查询条件
db.user.estimatedDocumentCount()
使用聚合管道进行复杂统计
当统计需求涉及分组、求和、求平均值、多条件过滤等复杂逻辑时,就需要使用mongodb的聚合管道(aggregate)功能。聚合管道由多个阶段(stage)组成,每个阶段对数据进行一步处理,最终输出统计结果。
常用聚合阶段说明
| 阶段名称 | 作用说明 |
|---|---|
| $match | 过滤数据,只保留符合条件的文档进入下一个阶段,类似查询中的where条件 |
| $group | 按指定字段对文档进行分组,在分组内执行求和、计数、求平均等聚合操作 |
| $project | 修改输出文档的结构,可以选择保留或丢弃字段,也可以新增计算字段 |
| $sort | 对结果进行排序,支持按单个或多个字段升序降序排列 |
| $limit | 限制返回的文档数量,一般用于分页或者取前N条结果 |
分组统计示例
假设我们有一个订单集合order,每个文档包含用户ID、订单金额、订单状态等字段,现在需要统计每个用户的订单总数量和总消费金额。
db.order.aggregate([
// 第一步:过滤出已支付的订单,只统计有效订单
{
$match: {
status: "paid"
}
},
// 第二步:按用户ID分组,统计每个用户的订单数和总金额
{
$group: {
_id: "$user_id", // 分组字段,按user_id分组
order_count: { $sum: 1 }, // 订单数,每有一个文档就加1
total_amount: { $sum: "$amount" } // 总消费金额,累加每个订单的amount字段
}
},
// 第三步:调整输出字段,让结果更易读
{
$project: {
_id: 0, // 不显示默认的_id字段
user_id: "$_id", // 把分组的_id重命名为user_id
order_count: 1,
total_amount: 1
}
},
// 第四步:按总消费金额降序排序
{
$sort: {
total_amount: -1
}
}
])
上面的聚合管道会先过滤出状态为已支付的订单,然后按用户ID分组,计算每个用户的订单数量和总金额,之后调整输出字段的格式,最后按总消费金额从高到低排序,输出的结果每个文档对应一个用户的统计信息。
不同统计场景的选型建议
- 如果只是统计文档总数或者符合简单条件的文档数量,优先使用
countDocuments或者estimatedDocumentCount,性能更好实现更简单。 - 如果统计需要分组、多字段计算、多步骤处理,必须使用聚合管道,它是mongodb处理复杂统计的核心能力。
- 如果统计的数据量非常大,建议在聚合管道的第一个阶段使用
$match过滤数据,减少后续阶段处理的数据量,提升统计性能。 - 如果统计结果需要排序或者分页,可以在聚合管道的末尾添加
$sort和$limit阶段实现对应需求。
注意事项
在使用聚合管道统计的时候,分组字段如果是字符串类型,需要注意字段的大小写问题,mongodb默认是区分大小写的,比如"User"和"user"会被当成两个不同的分组。如果不需要区分大小写,可以在$group阶段之前先使用$project把字段转成统一的大小写。
另外,聚合管道的处理是分阶段执行的,每个阶段只会接收上一个阶段输出的数据,所以阶段的顺序很重要,一般先过滤再分组,先分组再排序,符合数据处理的逻辑顺序可以提升执行效率。