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
相关产品推荐
相关产品推荐

