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

Mongoose中如何用单个findOneAndUpdate实现数组对象的增改?

用单次findOneAndUpdate实现数组元素的存在覆盖/不存在新增

我的模型

const mongoose = require('mongoose');

const TelemetrySchema = new mongoose.Schema({
  MQTT: {
    Id: { type: String, trim: true, required: true },
  },
  Presets: [{
    Id: Number,
    Message: String,
  }],
});

module.exports = mongoose.model('Telemetry', TelemetrySchema);

需求说明

我需要实现:当接收单个Preset对象时,若Presets数组中已有相同Id的对象则覆盖它,若不存在则添加该新对象。预期的数组变化如下:

  • t=0: 接收{Id: 1, Message: 'A'} → 数组变为[{Id: 1, Message: 'A'}]
  • t=1: 接收{Id: 2, Message: 'B'} → 数组变为[{Id: 1, Message: 'A'}, {Id: 2, Message: 'B'}]
  • t=3: 接收{Id: 1, Message: 'Z'} → 数组变为[{Id: 1, Message: 'Z'}, {Id: 2, Message: 'B'}]

现有方案需要两次数据库操作,想知道能不能用单次findOneAndUpdate完成?

现有方案代码:

client.on('message', async (topic, payload) => {
  const s = payload.toString();
  const p = JSON.parse(s);
  const id = topic.split('/');

  const existingTelemetry = await Telemetry.findOne({
    'MQTT.Id': id[1],
    'Presets.Id': p.Id,
  });

  if (existingTelemetry) {
    // 若存在相同Id的对象,更新现有对象
    await Telemetry.findOneAndUpdate(
      {
        'MQTT.Id': id[1],
        'Presets.Id': p.Id,
      },
      {
        $set: {
          'Presets.$': p,
        },
      },
      {
        new: true,
        omitUndefined: true,
      },
    );
  } else {
    // 若不存在相同Id的对象,添加新对象
    await Telemetry.findOneAndUpdate(
      {
        'MQTT.Id': id[1],
      },
      {
        $addToSet: {
          Presets: p,
        },
      },
      {
        upsert: true,
        new: true,
        omitUndefined: true,
      },
    );
  }
});

解决方案:使用MongoDB聚合管道更新

可以通过聚合管道在单次findOneAndUpdate中实现逻辑,利用$cond判断数组中是否存在目标Id,分别执行更新或新增操作:

client.on('message', async (topic, payload) => {
  const s = payload.toString();
  const p = JSON.parse(s);
  const mqttId = topic.split('/')[1];

  const result = await Telemetry.findOneAndUpdate(
    { 'MQTT.Id': mqttId },
    [
      // 判断Presets数组中是否存在Id等于p.Id的元素,分支处理
      {
        $set: {
          Presets: {
            $cond: {
              if: { $in: [p.Id, '$Presets.Id'] },
              // 存在则遍历数组,替换匹配Id的元素
              then: {
                $map: {
                  input: '$Presets',
                  as: 'item',
                  in: {
                    $cond: {
                      if: { $eq: ['$$item.Id', p.Id] },
                      then: p,
                      else: '$$item'
                    }
                  }
                }
              },
              // 不存在则将新元素追加到数组末尾
              else: { $concatArrays: ['$Presets', [p]] }
            }
          }
        }
      }
    ],
    { 
      upsert: true, // 若对应Telemetry文档不存在则自动创建
      new: true,
      omitUndefined: true 
    }
  );

  console.log('更新结果:', result);
});

原理说明

  1. 存在性判断:用$in: [p.Id, '$Presets.Id']检查目标Id是否在数组元素的Id集合中
  2. 替换逻辑:通过$map遍历数组,遇到Id匹配的元素就替换为新的p对象,其余元素保持不变
  3. 新增逻辑:用$concatArrays把新元素追加到原数组末尾
  4. upsert参数:确保当对应的Telemetry文档不存在时,自动创建新文档并初始化Presets数组

注意事项

  • 该方案要求MongoDB版本在4.2及以上,因为从4.2开始支持在更新操作中使用聚合管道
  • 逻辑天然保证Presets数组不会出现重复Id,因为存在时直接替换而非新增

内容的提问来源于stack exchange,提问作者MeerArtefakt

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 01:52:24