如何在MongoDB已排序聚合结果中抽取大集合的每第N条记录
解决方案
前置准备
首先为集合创建复合索引,保证筛选和排序阶段都可以命中索引,避免内存排序带来的性能损耗:
db.yourCollectionName.createIndex({ searches: 1, hour: -1 })
推荐方案(MongoDB 5.0+,支持百万级数据高效执行)
使用MongoDB 5.0引入的$setWindowFields窗口函数实现排序编号,性能远高于传统的分页或全量分组方案:
const N = 20000; // 间隔抽取的条数 db.yourCollectionName.aggregate([ // 筛选符合条件的记录 { $match: { searches: { $in: [ObjectId('614965487d5d1c55794ce670')], }, }, }, // 按hour降序排序 { $sort: { hour: -1 } }, // 按排序顺序为每条记录生成连续编号 { $setWindowFields: { sortBy: { hour: -1 }, output: { docRank: { $documentNumber: {} } } } }, // 过滤出第1条、每第N条记录 { $match: { $expr: { $or: [ { $eq: [ { $mod: [ "$docRank", N ] }, 0 ] }, { $eq: [ "$docRank", 1 ] } ] } } }, // 仅保留hour字段 { $project: { _id: 0, hour: 1 } } ])
最后一条记录补充
上述查询会返回第1、20000、40000...条的hour值,如果需要包含排序后的最后一条记录,可以在拿到结果后,单独执行以下查询获取最小hour(降序排序的最后一条对应hour最小),手动加入结果集即可:
db.yourCollectionName.find({ searches: { $in: [ObjectId('614965487d5d1c55794ce670')] } }).sort({ hour: 1 }).limit(1).hint({ searches: 1, hour: -1 })
兼容方案(MongoDB 5.0以下版本)
如果你的MongoDB版本不支持窗口函数,可以使用全量分组后按索引取值的方案,适合符合筛选条件的记录数在百万以内的场景:
const N = 20000; db.yourCollectionName.aggregate([ { $match: { searches: { $in: [ObjectId('614965487d5d1c55794ce670')] } } }, { $sort: { hour: -1 } }, // 将所有符合条件的hour存入数组 { $group: { _id: null, hours: { $push: "$hour" } } }, // 按间隔N取值 { $project: { _id: 0, sampleHours: { $reduce: { input: { $range: [0, { $size: "$hours" }, N] }, initialValue: [], in: { $concatArrays: ["$$value", [{ $arrayElemAt: ["$hours", "$$this"] }]] } } } } } ])
该方案的缺点是单条BSON有16MB上限,如果符合条件的记录数过多会触发查询报错,优先使用窗口函数方案。
内容的提问来源于stack exchange,提问作者Martin Ratinaud
相关产品推荐
相关产品推荐

