MongoDB聚合查询如何对分组后的嵌套对象列表进行限制与分页
解决方案
方案1:$slice 裁剪数组(轻量场景适用)
直接在聚合管道中新增$addFields阶段,使用$slice操作符对分组后的logs数组做截取,支持固定长度限制和分页逻辑,写法简单易维护。
代码示例:
const db = request.client.db( DATABASE_NAME ); // 自定义每组日志的分页参数 const groupLogsPage = 1; // 组内页码,从1开始 const groupLogsPageSize = 10; // 组内每页条数 const skipLogsCount = (groupLogsPage - 1) * groupLogsPageSize; db.collection( COLLECTION_NAME ) .aggregate([ // 先对原始日志按规则排序,保证分组后数组顺序符合预期,这里示例按时间倒序 { $sort: { time: -1 } }, { $group : { _id : "$device.model", logs: { $push: "$$ROOT" } } } , { $sort: { _id: 1 } }, // 新增阶段:裁剪logs数组,实现长度限制/分页 { $addFields: { logs: { $slice: ["$logs", skipLogsCount, groupLogsPageSize] } } }, // 根级分页移至管道内,逻辑更统一 { $skip: 5 }, { $limit: 2 } ]) .toArray() .then( docs => { response.send({ status: STATUS_SUCCESS, data: docs }); });
适用场景:单分组下日志数量较少(千级以内),不需要考虑额外内存开销的场景。
方案2:$setWindowFields 预过滤(大数据量场景适用,MongoDB 5.0+支持)
先给每个设备型号分组内的日志标记序号,直接过滤出分页范围内的日志后再做聚合,避免加载多余数据,内存开销更低。
代码示例:
const db = request.client.db( DATABASE_NAME ); // 自定义每组日志的分页参数 const groupLogsPage = 1; // 组内页码,从1开始 const groupLogsPageSize = 10; // 组内每页条数 const minIndex = (groupLogsPage - 1) * groupLogsPageSize + 1; const maxIndex = groupLogsPage * groupLogsPageSize; db.collection( COLLECTION_NAME ) .aggregate([ // 按设备型号分区,分区内按时间倒序,给每个文档生成分组内序号 { $setWindowFields: { partitionBy: "$device.model", sortBy: { time: -1 }, output: { groupIndex: { $documentNumber: {} } } } }, // 过滤出当前分页范围内的日志 { $match: { groupIndex: { $gte: minIndex, $lte: maxIndex } } }, // 分组聚合 { $group : { _id : "$device.model", logs: { $push: "$$ROOT" } } } , { $sort: { _id: 1 } }, // 根级分页 { $skip: 5 }, { $limit: 2 } ]) .toArray() .then( docs => { response.send({ status: STATUS_SUCCESS, data: docs }); });
适用场景:单分组下日志数量较大(万级以上),对查询性能要求较高的场景。
注意事项
- 若需要自定义分组内日志的排序规则,必须在聚合的前置阶段指定排序逻辑,避免截取的日志顺序不符合预期。
- 根级的
skip和limit放在聚合管道内和游标方法调用效果一致,放在管道内更便于排查复杂聚合逻辑的问题。
内容的提问来源于stack exchange,提问作者Vito Lipari
相关产品推荐
相关产品推荐

