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); });
原理说明
- 存在性判断:用
$in: [p.Id, '$Presets.Id']检查目标Id是否在数组元素的Id集合中 - 替换逻辑:通过
$map遍历数组,遇到Id匹配的元素就替换为新的p对象,其余元素保持不变 - 新增逻辑:用
$concatArrays把新元素追加到原数组末尾 - upsert参数:确保当对应的Telemetry文档不存在时,自动创建新文档并初始化Presets数组
注意事项
- 该方案要求MongoDB版本在4.2及以上,因为从4.2开始支持在更新操作中使用聚合管道
- 逻辑天然保证
Presets数组不会出现重复Id,因为存在时直接替换而非新增
内容的提问来源于stack exchange,提问作者MeerArtefakt
相关产品推荐
相关产品推荐

