求助:按名称和时间间隔聚合MongoDB商品价格数据
MongoDB聚合查询:按商品获取不同时间点的价格
我明白你找了好几天都没搞定这个查询,别着急,咱们一步步来解决这个问题。
首先看你手里的原始数据,是按商品分组的时间序列价格记录:
[ { name: 'apple', date: ISODate("2018-01-04T10:00:00.000Z"), price: 100 }, { name: 'apple', date: ISODate("2018-01-04T10:01:00.000Z"), price: 101 }, { name: 'apple', date: ISODate("2018-01-04T10:02:00.000Z"), price: 102 }, { name: 'apple', date: ISODate("2018-01-04T10:03:00.000Z"), price: 103 }, { name: 'apple', date: ISODate("2018-01-04T10:04:00.000Z"), price: 102 }, { name: 'apple', date: ISODate("2018-01-04T10:05:00.000Z"), price: 104 }, { name: 'cherry', date: ISODate("2018-01-04T10:00:00.000Z"), price: 53 }, { name: 'cherry', date: ISODate("2018-01-04T10:01:00.000Z"), price: 55 }, { name: 'cherry', date: ISODate("2018-01-04T10:02:00.000Z"), price: 51 }, { name: 'cherry', date: ISODate("2018-01-04T10:03:00.000Z"), price: 51 }, { name: 'cherry', date: ISODate("2018-01-04T10:04:00.000Z"), price: 50 }, { name: 'cherry', date: ISODate("2018-01-04T10:05:00.000Z"), price: 52 }, { name: 'melon', date: ISODate("2018-01-04T10:00:00.000Z"), price: 133 }, { name: 'melon', date: ISODate("2018-01-04T10:01:00.000Z"), price: 132 }, { name: 'melon', date: ISODate("2018-01-04T10:02:00.000Z"), price: 136 }, { name: 'melon', date: ISODate("2018-01-04T10:03:00.000Z"), price: 137 }, { name: 'melon', date: ISODate("2018-01-04T10:04:00.000Z"), price: 138 }, { name: 'melon', date: ISODate("2018-01-04T10:05:00.000Z"), price: 140 } ]
你想要的结果是每个商品一行,包含最新价格、1分钟前的价格、5分钟前的价格:
[ {name: 'apple', price_last: 104, price_1m_ago: 102, price_5m_ago: 100}, {name: 'cherry', price_last: 52, price_1m_ago: 50, price_5m_ago: 53}, {name: 'melon', price_last: 140, price_1m_ago: 138, price_5m_ago: 133} ]
你之前尝试了基础的$group只拿到了最新价格,确实还不够,咱们需要扩展聚合管道来匹配不同时间点的价格。
解决方案:完整聚合查询
这里给你写好完整的聚合语句,我会一步步解释每个阶段的作用:
food.aggregate([ // 第一步:按商品分组,把每个商品的所有价格记录按日期排序后存到数组里 { $group: { _id: "$name", priceHistory: { $push: { date: "$date", price: "$price" } } } }, // 第二步:对每个商品的价格历史数组按日期升序排序(确保时间顺序正确) { $addFields: { sortedHistory: { $sortArray: { input: "$priceHistory", sortBy: { date: 1 } } } } }, // 第三步:提取需要的三个时间点价格 { $project: { _id: 0, name: "$_id", price_last: { $last: "$sortedHistory.price" }, price_1m_ago: { $let: { vars: { targetDate: { $subtract: [ { $last: "$sortedHistory.date" }, 60000 ] } // 最新时间减1分钟(60秒=60000毫秒) }, in: { $getField: { field: "price", input: { $first: { $filter: { input: "$sortedHistory", cond: { $gte: [ "$$this.date", "$$targetDate" ] } } } } } } } }, price_5m_ago: { $let: { vars: { targetDate: { $subtract: [ { $last: "$sortedHistory.date" }, 300000 ] } // 最新时间减5分钟(300秒=300000毫秒) }, in: { $getField: { field: "price", input: { $first: { $filter: { input: "$sortedHistory", cond: { $gte: [ "$$this.date", "$$targetDate" ] } } } } } } } } } } ])
关键步骤解释
- $group阶段:把同一个商品的所有记录收集到
priceHistory数组里,这样我们就能拿到每个商品的完整时间序列数据。 - $addFields + $sortArray:确保价格历史是按时间升序排列的,这样
$last就能准确拿到最新的记录,过滤的时候也能按顺序找到最接近目标时间的那条数据。 - $project阶段:
price_last直接用$last取排序后数组的最后一个价格,就是最新价格。price_1m_ago和price_5m_ago的逻辑一致:先计算目标时间(最新时间减去对应分钟数),然后用$filter找出所有晚于等于目标时间的记录,再用$first取最接近目标时间的那条,最后提取它的价格。
如果你的数据里每个时间点(比如整分钟)都有一条记录,这个查询会完美匹配你的需求;如果有缺失的时间点,它会取目标时间之后的第一条记录,也是合理的业务逻辑。
内容的提问来源于stack exchange,提问作者COS
相关产品推荐
相关产品推荐

