HOWTO · MongoDB
MongoDB 中的 SELECT COUNT GROUP BY
本教程将介绍 MongoDB 中的操作以及 MongoDB 中聚合操作的使用。此外,这将详细讨论如何使用聚合函数对组进行计数和排序。
本页内容
要按某个字段、多个字段的组合或日期统计文档数量,可以使用 MongoDB 聚合管道。管道中的各阶段按顺序转换文档;$group 用 _id 定义每个输出组,而 $sum: 1 用于统计进入该组的文档数。统计数组元素前先使用 $unwind,再用 $sort 排序结果。
下面的示例使用 students 集合,其中有 name、city 和 courses 字段;日期示例则使用另一个 sales 集合。请将这些名称替换为你自己的集合名和字段名。
按一个字段统计文档数量:$group
按你想查看不同取值的字段分组。例如,db.students.aggregate([{$group: {_id: "$city", count: {$sum: 1}}}]) 会为每个城市生成一条结果。每条结果的 _id 是城市值,count 是该组的文档数。$sum: 1 会为每个进入分组的文档加一;它不会统计另一个字段的不同取值数量。
下面用普通 Python 值展示这一计数模式的简化模型。这只是语言层面的演示,不是 MongoDB 查询,也不构成运行时证据。
运行此示例:
本节中的代码以及显示的标签或输出仍为英文。
students = [{"city": "Canada"}, {"city": "Canada"}, {"city": "Japan"}]
counts = {}
for student in students:
city = student["city"]
counts[city] = counts.get(city, 0) + 1
结果映射中,Canada 对应 2,Japan 对应 1。在 MongoDB 中,对应的分组键是字段路径 "$city"。
按多个字段统计文档数量
若要统计每种字段组合,请将包含所有分组字段的对象设为 _id。例如,db.students.aggregate([{$group: {_id: {city: "$city", name: "$name"}, count: {$sum: 1}}}]) 会分别统计每个城市与姓名组合的文档数。MongoDB 会将整个 _id 对象作为分组键,因此任一字段值不同,都会形成不同的组。添加或移除 _id 中的属性即可调整组合方式,无须更改计数表达式。
按组统计文档数时,请在 $group 中使用 $sum: 1。$count 阶段则统计到达该阶段的文档,并返回一个计数字段;如果需要分别计算每个组的数量,它不能替代累加器。
使用 $unwind 和 $sortByCount 统计数组值
统计数组中的成员前,需要先将数组字段展开为单独的值。管道 db.students.aggregate([{$unwind: "$courses"}, {$sortByCount: "$courses"}]) 会为每个课程元素生成一条管道文档,随后将相同课程值分组,并按计数从高到低排序。一个学生有三门课程,就会分别为这三门课程的计数贡献一次。
$sortByCount 是一种简写,表示按表达式分组、用 $sum: 1 累加,并按计数降序排序。如果同一门课程在某个学生的数组中重复出现,它统计的是出现次数,而不是每门课程的独立学生数。如果需要区分这两种情况,可以在展开数组前去重,或先按学生和课程两者分组来构造管道。
对分组计数排序
在 $group 后添加 $sort,即可明确指定排序方式。对于按城市分组的单字段结果,{$sort: {_id: 1}} 会按城市键升序排列,{$sort: {_id: -1}} 则按降序排列。对于复合分组,可以按 {"_id.city": 1} 这样的嵌套键排序。若要按出现频率排名,请使用 {$sort: {count: -1}};1 表示升序,-1 表示降序。
例如,db.students.aggregate([{$group: {_id: "$city", count: {$sum: 1}}}, {$sort: {count: -1, _id: 1}}]) 会先将数量最多的组排在前面,并在数量相同时按城市键排序。按分组结果中不存在的字段排序,无法得到预期顺序;因此应按 _id 或 count 之类的累加结果排序。
按日期分组并计算数量与总额
先筛选目标日期范围,再将每个时间戳规范化为日历日期,并在 $group 中同时计算数量和总额。下面是按 UTC 日期统计 2021 年数据的示例管道:db.sales.aggregate([{$match: {date: {$gte: ISODate("2021-01-01T00:00:00Z"), $lt: ISODate("2022-01-01T00:00:00Z")}}}, {$group: {_id: {$dateToString: {format: "%Y-%m-%d", date: "$date", timezone: "UTC"}}, count: {$sum: 1}, totalSales: {$sum: {$multiply: ["$price", "$quantity"]}}}}, {$sort: {_id: 1}}])。
起始边界包含在范围内,而下一年的起始时间不包含在范围内,因此会纳入 2021 年内的时间戳,排除 2022 年的数据。$dateToString 会将同一 UTC 日期的时间戳归入同一组。$sum: 1 统计销售文档数;$multiply 先计算每条记录的价格乘以数量,再由 $sum 汇总这些金额。请使用与报表日期相符的时区;业务所在地的日期边界可能与 UTC 不一致。确保 date、price 和 quantity 的值分别适合日期和数值运算,并按需要调整日期范围。