MongoDB聚合查询问题:按likedVideos顺序返回数据并实现分页
解决MongoDB关联查询点赞视频排序及分页问题
问题根源分析
你的查询存在两个核心问题:
$lookup默认返回的文档顺序不与原likedVideos数组顺序一致,MongoDB批量关联查询的结果顺序由内部逻辑决定,而非原数组的点赞顺序;- 先
unwind再group的操作会打乱数组原有顺序,后续基于错误顺序的$slice分页自然无法得到正确结果。
修正方案一(通用版本)
通过保留原始数组索引来确保排序正确性,再执行分页:
const { userId, skip, limit } = req.query; const pipeline = [ { $match: { _id: ObjectId(userId), }, }, // 为每个点赞视频ID添加原始数组索引,保留点赞顺序 { $addFields: { likedWithIndex: { $map: { input: "$likedVideos", as: "videoId", in: { videoId: "$$videoId", index: { $indexOfArray: ["$likedVideos", "$$videoId"] } } } } } }, { $lookup: { from: "videos", localField: "likedWithIndex.videoId", foreignField: "_id", as: "likedList" } }, // 将关联结果与原始索引绑定,确保每个视频对应正确的点赞顺序 { $addFields: { likedList: { $map: { input: "$likedWithIndex", as: "item", in: { $mergeObjects: [ { $arrayElemAt: ["$likedList", { $indexOfArray: ["$likedList._id", "$$item.videoId"] }] }, { index: "$$item.index" } ] } } } } }, // 按索引倒序,让最新点赞的视频(原数组末尾,索引最大)排在最前 { $unwind: "$likedList" }, { $sort: { "likedList.index": -1 } }, // 基于正确顺序执行分页 { $skip: parseInt(skip) }, { $limit: parseInt(limit) }, // 重新分组并清理中间字段 { $group: { _id: "$_id", likedList: { $push: { _id: "$likedList._id", videoUrl: "$likedList.videoUrl" } } // 按需保留字段 } } ]; const likedVideos = await User.aggregate(pipeline); return res.status(200).send(likedVideos);
修正方案二(高效简洁版本,MongoDB 3.6+支持)
利用$lookup的子Pipeline直接完成关联、排序、分页,减少中间操作:
const { userId, skip, limit } = req.query; const pipeline = [ { $match: { _id: ObjectId(userId) } }, { $lookup: { from: "videos", let: { likedIds: "$likedVideos" }, pipeline: [ { $match: { $expr: { $in: ["$_id", "$$likedIds"] } } }, // 添加原始数组中的点赞位置索引 { $addFields: { index: { $indexOfArray: ["$$likedIds", "$_id"] } } }, // 按索引倒序,最新点赞排前面 { $sort: { index: -1 } }, // 执行分页 { $skip: parseInt(skip) }, { $limit: parseInt(limit) }, // 移除中间索引字段 { $project: { index: 0 } } ], as: "likedList" } }, // 可选:只返回需要的字段 { $project: { likedList: 1, _id: 0 } } ]; const likedVideos = await User.aggregate(pipeline); return res.status(200).send(likedVideos);
关键改进点
- 保留原始顺序:通过
$indexOfArray记录每个视频在原点赞数组中的位置,确保排序依据正确; - 先排序后分页:在确定正确顺序后再执行
$skip和$limit,保证分页结果符合懒加载需求; - 简化操作链路:方案二将关联、排序、分页逻辑整合到
$lookup内部,减少了unwind和group的性能损耗。
内容的提问来源于stack exchange,提问作者Kien Pham
相关产品推荐
相关产品推荐

