
本文详解如何使用 MongoDB 聚合管道($unwind、$group、$setWindowFields、$lookup)从用户进度数组中统计各冥想课程被练习次数,并精准获取“最常练习”的课程及其完整信息。
本文详解如何使用 MongoDB 聚合管道($unwind、$group、$setWindowFields、$lookup)从用户进度数组中统计各冥想课程被练习次数,并精准获取“最常练习”的课程及其完整信息。
在构建冥想类应用的数据看板时,一个常见需求是:找出被用户练习次数最多的冥想课程(Meditation)。由于用户练习记录以子文档形式嵌套在 userProgress.meditationsPracticed 数组中,且仅存储了冥想 ID 字符串(如 "5ef2ff0af1a23752be00651f"),而完整课程信息(如名称、时长)独立存于 meditations 集合,因此必须通过聚合管道完成「拆解→计数→排名→关联」四步操作。
以下是完整、可复用的聚合查询流程(适用于 MongoDB 5.0+,支持 $setWindowFields):
db.users.aggregate([
// 步骤1:可选 —— 按需筛选特定用户(如调试单个用户)
{
"$match": {
"_id": { "$oid": "627b519f73b2bd3375f5a7a5" }
}
},
// 步骤2:展开数组 —— 将每个练习记录转为独立文档
{
"$unwind": "$userProgress.meditationsPracticed"
},
// 步骤3:按冥想ID分组计数 —— 注意:字符串ID需转为ObjectId才能关联
{
"$group": {
"_id": { "$toObjectId": "$userProgress.meditationsPracticed.id" },
"count": { "$sum": 1 }
}
},
// 步骤4:窗口函数排序排名 —— 按count降序,计算全局排名(处理并列情况)
{
"$setWindowFields": {
"sortBy": { "count": -1 },
"output": {
"rank": { "$rank": {} }
}
}
},
// 步骤5:筛选排名第一的冥想(即最高频次;若需Top N,改用 $gte: 1)
{
"$match": { "rank": 1 }
},
// 步骤6:关联冥想主表 —— 获取name、duration等完整元数据
{
"$lookup": {
"from": "meditations",
"localField": "_id",
"foreignField": "_id",
"as": "meditationInfo"
}
},
// 步骤7:解构lookup结果 —— 确保返回单个对象而非数组(假设_id唯一)
{
"$unwind": {
"path": "$meditationInfo",
"preserveNullAndEmptyArrays": true
}
},
// 步骤8(推荐):投影最终字段,提升可读性
{
"$project": {
"_id": "$_id",
"meditationId": "$_id",
"name": "$meditationInfo.name",
"duration": "$meditationInfo.duration",
"practiceCount": "$count",
"_class": "$meditationInfo._class" // 如有需要可保留其他字段
}
}
])
✅ 关键要点说明:
-
"$toObjectId"是核心转换步骤:原始meditationsPracticed.id是字符串,而meditations._id是 ObjectId 类型,直接$lookup会失败,必须显式转换; -
$setWindowFields+$rank可正确处理「多个冥想并列最高频次」的情况(例如两个冥想都被练习了 12 次),避免仅取第一条导致遗漏; - 若需获取 Top 3 最常练习课程,将步骤5的
"$match": { "rank": { "$lte": 3 } }即可; - 生产环境建议在
userProgress.meditationsPracticed.id字段上建立复合索引(如{ "userProgress.meditationsPracticed.id": 1 })以加速$unwind和$group阶段。
? 进阶提示:
如需全量统计(所有用户汇总,而非单个用户),请移除初始 $match 步骤,并确保 $unwind 后仍能准确归因——此时可添加 $group 先按用户分组再跨用户累加,或直接全局计数(取决于业务定义的“最常练习”是否含去重逻辑)。
该方案结构清晰、语义明确,兼顾准确性与可维护性,是处理「嵌套数组内ID关联外部集合」类分析场景的标准实践。











