You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 09:10:50