MongoDB单查询获取各产品多时间段价格的实现咨询
实现单查询获取多时间段产品价格
当然可以!你完全可以通过单条MongoDB聚合查询一次性获取所有产品在多个时间节点的价格数据,不用反复发起多次查询。针对你的场景,我们可以利用MongoDB的$facet聚合操作来并行处理多个时间范围的查询,再将结果合并成你需要的格式。
核心思路
$facet允许在一个聚合管道中同时运行多个独立的子管道,每个子管道负责获取一个时间节点的价格数据,最后我们把这些子管道的结果按产品名称合并,就能得到你期望的输出格式。结合你用的unix_date时间戳进行匹配,性能会比用ISODate更好。
完整聚合查询代码
// 先计算各个时间节点的时间戳(当前时间减去对应时长) const currentTimestamp = Date.now(); const timeOffsets = { 10s: currentTimestamp - 10 * 1000, 30s: currentTimestamp - 30 * 1000, 1m: currentTimestamp - 60 * 1000, 5m: currentTimestamp - 5 * 60 * 1000, 15m: currentTimestamp - 15 * 60 * 1000 }; // 执行聚合查询 food.aggregate([ // 第一步:用$facet并行处理每个时间范围的价格查询 { $facet: { latest: [ // 获取每个产品的最新价格 { $group: { _id: "$name", price: { $last: "$price" } } } ], price_10s: [ // 获取10秒前的最新价格(筛选时间戳<=10秒前的记录,再取每个产品的最后一条) { $match: { unix_date: { $lte: timeOffsets['10s'] } } }, { $group: { _id: "$name", price_10s: { $last: "$price" } } } ], price_30s: [ { $match: { unix_date: { $lte: timeOffsets['30s'] } } }, { $group: { _id: "$name", price_30s: { $last: "$price" } } } ], price_1m: [ { $match: { unix_date: { $lte: timeOffsets['1m'] } } }, { $group: { _id: "$name", price_1m: { $last: "$price" } } } ], price_5m: [ { $match: { unix_date: { $lte: timeOffsets['5m'] } } }, { $group: { _id: "$name", price_5m: { $last: "$price" } } } ], price_15m: [ { $match: { unix_date: { $lte: timeOffsets['15m'] } } }, { $group: { _id: "$name", price_15m: { $last: "$price" } } } ] } }, // 第二步:展开各个facet的结果数组(保留空数组的情况,避免丢失产品) { $unwind: "$latest" }, { $unwind: { path: "$price_10s", preserveNullAndEmptyArrays: true } }, { $unwind: { path: "$price_30s", preserveNullAndEmptyArrays: true } }, { $unwind: { path: "$price_1m", preserveNullAndEmptyArrays: true } }, { $unwind: { path: "$price_5m", preserveNullAndEmptyArrays: true } }, { $unwind: { path: "$price_15m", preserveNullAndEmptyArrays: true } }, // 第三步:按产品名称合并所有时间节点的价格数据 { $group: { _id: "$latest._id", price: { $first: "$latest.price" }, price_10s: { $first: "$price_10s.price_10s" }, price_30s: { $first: "$price_30s.price_30s" }, price_1m: { $first: "$price_1m.price_1m" }, price_5m: { $first: "$price_5m.price_5m" }, price_15m: { $first: "$price_15m.price_15m" } } }, // 第四步:处理空值(可选,比如没有数据时显示null或默认值) { $project: { price: 1, price_10s: { $ifNull: ["$price_10s", null] }, price_30s: { $ifNull: ["$price_30s", null] }, price_1m: { $ifNull: ["$price_1m", null] }, price_5m: { $ifNull: ["$price_5m", null] }, price_15m: { $ifNull: ["$price_15m", null] } } } ]) .then(results => { // 输出结果就是你期望的格式 console.log(results); }) .catch(err => { console.error("查询出错:", err); });
关键细节说明
$facet的优势:并行处理多个子查询,减少数据库交互次数,尤其适合你这种高频查询的场景,能显著提升性能。- 时间戳匹配:用
unix_date(数字类型)进行范围匹配比ISODate更高效,因为数字比较的运算成本更低。 - 保留空数据:
preserveNullAndEmptyArrays: true确保即使某个产品在某个时间段没有历史数据,也不会被过滤掉,而是保留对应字段为null(你可以根据需求改成默认值,比如0)。 - 索引优化:为了让查询更快,建议创建复合索引:
这个索引能让每个子管道的db.food.createIndex({ name: 1, unix_date: -1 })$match和$group操作直接利用索引快速定位到每个产品的最新记录,避免全表扫描。
额外优化建议
- TTL索引清理旧数据:如果不需要保留太久的历史价格,可以设置TTL索引自动删除过期数据,减少数据量:
// 保留1小时的历史数据(根据你的需求调整expireAfterSeconds的值) db.food.createIndex({ date: 1 }, { expireAfterSeconds: 3600 }) - 内存限制处理:如果产品数量极多,聚合查询可能超过MongoDB默认的100MB内存限制,可以在聚合时添加
allowDiskUse: true参数,允许使用磁盘临时存储数据:food.aggregate([/* 管道内容 */], { allowDiskUse: true })
内容的提问来源于stack exchange,提问作者COS
相关产品推荐
相关产品推荐

