MongoDB findOneAndUpdate:如何避免向数组插入重复对象?
解决MongoDB数组对象去重插入问题(按dateTime和title校验)
需求:MongoDB文档包含reports对象数组属性,向该数组添加新对象时,若数组中已存在dateTime和title均相同的对象,则不执行插入;若不存在,则插入新对象。
文档结构
{ "_id" : ObjectId("637b99b36cc39a1fee0a4977"), "name" : "product1", "reports" : [ { "_id" : ObjectId("63f60fe2029fcd31b93870d3"), "name" : "product1", "title" : "hi", "dateTime" : ISODate("2023-02-22T12:51:31.993+0000"), "createdAt" : ISODate("2023-02-22T12:51:46.351+0000") } ], .... }
待添加的reports数据
const reports = { 'xyz-xyz': [ { name: 'xyz-xyz', title: 'good morning', dateTime: '2022-11-09T07:18:05.000Z', ... }, { name: 'xyz-xyz', title: 'hi', dateTime: '2022-11-09T07:18:06.000Z', ... }, ], xxx: [ { name: 'xxx', title: 'hello', dateTime: '2022-11-09T07:19:05.000Z', ... }, ] }
错误尝试分析
- 第一段代码错误使用
$inc做日期比较,且$in的写法无法在MongoDB查询上下文执行客户端数组map操作,语法完全无效。 - 第二段代码仅校验
dateTime字段,未满足同时校验title的需求,且直接拼接整个待添加数组,未逐个对象判断重复。
正确实现方法
使用MongoDB聚合管道实现多字段重复判断,逐个处理待添加数组中的对象,判断现有reports数组是否存在dateTime和title均匹配的元素,不存在则追加。
for (let key in reports) { // 遍历当前key对应的所有待添加report对象 for (const newReport of reports[key]) { // 将字符串日期转为MongoDB可识别的Date对象 const targetDateTime = new Date(newReport.dateTime); const targetTitle = newReport.title; await Station.findOneAndUpdate( { "name": key }, [ { "$set": { "reports": { "$cond": [ // 判断条件:reports数组中是否存在dateTime和title都匹配的元素 { "$anyElementTrue": { "$map": { "input": "$reports", "as": "r", "in": { "$and": [ { "$eq": ["$$r.dateTime", targetDateTime] }, { "$eq": ["$$r.title", targetTitle] } ] } } } }, // 存在重复则保持原数组 "$reports", // 无重复则追加新对象 { "$concatArrays": ["$reports", [newReport]] } ] } } } ], { new: true, upsert: true } // 可选:无对应文档时自动创建 ); } }
关键说明
- 用
$anyElementTrue+$map遍历现有数组,精准判断多字段重复。 - 必须将字符串日期转为
Date对象,确保与MongoDB的ISODate类型一致,避免比较失效。 - 双层循环逐个处理待添加对象,保证每个对象都能独立校验重复。
upsert: true为可选配置,可自动创建不存在的目标文档。
内容的提问来源于stack exchange,提问作者user6377312
相关产品推荐
相关产品推荐

