You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MongoDB聚合查询问题:按likedVideos顺序返回数据并实现分页

解决MongoDB关联查询点赞视频排序及分页问题

问题根源分析

你的查询存在两个核心问题:

  1. $lookup默认返回的文档顺序不与原likedVideos数组顺序一致,MongoDB批量关联查询的结果顺序由内部逻辑决定,而非原数组的点赞顺序;
  2. 先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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 15:01:34