MongoDB使用$in时如何获取每个deviceId的最新读数文档?
获取每个设备的最新读数文档(解决Mongoose聚合错误)
我来帮你搞定这个问题!首先,你碰到的「The field 'deviceId' must be an accumulator object」错误,根源是在MongoDB聚合的$group阶段里,除了_id字段之外,其他所有字段都必须用累加器操作符(比如$first、$max、$last这类)来声明,而你之前修改后的代码应该是直接写了deviceId: "$deviceId"这种语句,没有用累加器包裹,所以才触发了这个报错。
下面给你两种靠谱的实现方案,都能满足「为deviceIds数组里的每个设备返回最新创建文档」的需求:
方案一:排序后分组取最新(代码简洁,适合中等数据量)
这个思路是先筛选目标设备的数据,再按设备ID和创建时间降序排序,最后分组取每组的第一个文档(也就是最新的那条):
const latestReadings = await Reading.aggregate([ // 第一步:只筛选deviceIds数组里的设备数据 { $match: { deviceId: { $in: deviceIds } } }, // 第二步:按设备ID分组,同时按创建时间倒序排列,确保每组最前面的是最新文档 { $sort: { deviceId: 1, createdAt: -1 } }, // 第三步:分组,取每组的第一个文档作为最新读数 { $group: { _id: "$deviceId", latestReading: { $first: "$$ROOT" } // $$ROOT表示整个文档对象 } }, // 第四步:把嵌套的latestReading内容提为顶层文档,方便后续使用 { $replaceRoot: { newRoot: "$latestReading" } } ]);
方案二:先查最新时间再关联(性能更优,适合大数据量)
如果你的读数文档数量特别大(比如远超10000条),这个方案的性能会更好:先聚合出每个设备的最新创建时间,再通过关联查询找到对应时间的文档:
const latestReadings = await Reading.aggregate([ // 筛选目标设备 { $match: { deviceId: { $in: deviceIds } } }, // 分组获取每个设备的最新创建时间 { $group: { _id: "$deviceId", maxCreatedAt: { $max: "$createdAt" } } }, // 关联回原集合,找到对应设备+对应时间的文档 { $lookup: { from: "readings", // 这里要填你的MongoDB集合名称(通常是模型名的复数小写) let: { targetDeviceId: "$_id", targetTime: "$maxCreatedAt" }, pipeline: [ { $match: { $expr: { $and: [ { $eq: ["$deviceId", "$$targetDeviceId"] }, { $eq: ["$createdAt", "$$targetTime"] } ] } } } ], as: "latestReading" } }, // 把数组格式的结果转成单个文档 { $unwind: "$latestReading" }, // 提取顶层文档 { $replaceRoot: { newRoot: "$latestReading" } } ]);
为什么之前会报错?
举个例子,如果你之前的$group阶段写了类似下面的代码,就会触发那个错误:
// ❌ 错误写法:deviceId字段没有用累加器包裹 { $group: { _id: "$deviceId", deviceId: "$deviceId", // 这里直接引用字段,不符合MongoDB的聚合规则 latestReading: { $first: "$$ROOT" } } }
正确的做法是要么把deviceId作为_id(就像上面两个方案那样),要么用累加器声明它:deviceId: { $first: "$deviceId" },不过前者更简洁,没必要多此一举。
你可以根据自己的数据量选择合适的方案,两种都能完美解决你的需求~
内容的提问来源于stack exchange,提问作者Egidi
相关产品推荐
相关产品推荐

