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

Mongoose如何实现分类关联视频按总观看数分页排序查询

Mongoose 按关联视频总观看数排序分页实现方案

前置修正

你调整后的VideoSchema缺少关联字段的类型定义,必须指定为ObjectId才能正常做集合关联,修正后Schema如下:

const VideoSchema = new Schema<IVideo>({
  videoSource: String,
  category: {
    type: Schema.Types.ObjectId, // 必须加类型,指向Categories文档的_id
    ref: 'Categories'
  },
  viewers: Number,
});

核心实现思路

你之前的写法是典型的N+1查询问题:6万条分类会触发6万次独立的数据库查询,还要把全量数据拉到Node.js内存做计算、排序、切片,IO和内存开销极大,请求慢是必然的。
MongoDB聚合管道的$lookup操作等价于关系型数据库的LEFT JOIN,可以全程在数据库层面完成关联、统计、排序、分页逻辑,不需要把全量数据加载到应用层,性能提升几个数量级,同时天然满足「无对应视频的分类总观看数记为0」的需求。

完整实现代码

const resolver = async ({ args: { limit, offset } }) => {
  return await CategoriesModel.aggregate([
    // 1. 左连接Videos集合,等价于LEFT JOIN videos ON categories._id = videos.category
    {
      $lookup: {
        from: 'videos', // 这里填Videos集合的实际名称,Mongoose默认是模型名转小写复数,即videos
        localField: '_id',
        foreignField: 'category',
        as: 'matchedVideos'
      }
    },
    // 2. 计算当前分类下所有视频的观看数总和,无匹配视频时matchedVideos是空数组,$sum直接返回0
    {
      $addFields: {
        viewers: {
          $sum: '$matchedVideos.viewers'
        }
      }
    },
    // 3. 去掉临时生成的matchedVideos字段,只返回需要的业务字段
    {
      $project: {
        name: 1,
        image: 1,
        viewers: 1
      }
    },
    // 4. 按总观看数降序排列
    {
      $sort: {
        viewers: -1
      }
    },
    // 5. 分页跳过offset条
    {
      $skip: offset
    },
    // 6. 取当前页limit条数据
    {
      $limit: limit
    }
  // 数据量涨到百万级时可以打开下面的配置,允许排序时使用磁盘临时空间,避免内存溢出
  // ]).allowDiskUse(true)
  ]);
};

注意事项

  • 如果你的Videos集合自定义过名称,需要把$lookup里的from字段改成实际的集合名,否则会关联失败。
  • 该实现会返回全量分类的分页结果,没有任何视频的分类会被正常返回,viewers值为0,完全符合需求。
  • 所有计算、排序、分页操作全在MongoDB服务端完成,不需要应用层加载全量数据,6万分类的场景下查询耗时通常在几十毫秒级别。
  • 你原有实现里的slice(offset, limit)写法存在逻辑错误:Array.slice的第二个参数是结束位置下标而非截取长度,会导致分页结果不符合预期,聚合管道的$skip+$limit是标准数据库分页逻辑,不存在这个问题。

内容的提问来源于stack exchange,提问作者Marty

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 14:57:16