MongoDB聚合管道更新在Compass可用,Node驱动中无效问题
问题:无法仅更新嵌套子文档数组中的单个文档
问题描述
我需要只更新嵌套子文档数组里的第一个匹配项,但之前的查询会更新所有符合条件的文档。尝试用聚合管道加$limit来实现,但Node驱动里执行后没有实际更新数据库,也没报错。
旧查询问题
之前的updateOne语句会把所有userId为"empty"的条目都更新,不符合仅更新第一个的需求:
const query = await db.collection('events').updateOne({ _id: new ObjectId(eventId), createdBy: new ObjectId(createdBy), "weights.weight": weight }, { $set: { "weights.$.spotsAvailable.$[el2]": { "name": applicantName, "userId": new ObjectId(applicantId) } } }, { arrayFilters: [ { "el2.userId": "empty" } ] })
我在MongoDB Compass的聚合构建器里测试过聚合管道,能正常筛选出目标条目,但放到Node代码里执行后没有实际更新数据。
已尝试的操作
- 不使用
new ObjectId传入变量 - 使用
new ObjectId传入变量 - 直接用Compass里复制的硬编码值在Node驱动中运行聚合代码
示例文档
{ "_id": { "$oid": "6398c34ca67dbe3286452f23" }, "name": "test", "createdBy": { "$oid": "636c1778f1d09191074f9690" }, "description": "testing", "date": { "$date": { "$numberLong": "1645488000000" } }, "location": { "type": "Point", "coordinates": [ 0, 0 ] }, "weights": [ { "spotsAvailable": [ { "name": "empty", "userId": "empty" }, { "name": "empty", "userId": "empty" }, { "name": "empty", "userId": "empty" } ], "weight": 12 }, { "spotsAvailable": [ { "name": "empty", "userId": "empty" }, { "name": "empty", "userId": "empty" } ], "weight": 15 } ], "eventApplicants": [ { "userId": { "$oid": "636c1778f1d09191074f9690" }, "name": "Wayne Wrestler", "weight": 15 } ] }
当前尝试的聚合代码(无实际更新)
const acceptOrRemoveApplicant = async (eventId: ObjectId, createdBy: ObjectId, applicantId: ObjectId, applicantName: string, boolean: boolean, weight: number): Promise<boolean | undefined> => { console.log({ eventId, createdBy, applicantId, applicantName, boolean, weight }) if (boolean == true) { try { const agg = [ { '$match': { '_id': new ObjectId('6398c34ca67dbe3286452f23'), 'createdBy': new ObjectId('636c1778f1d09191074f9690') } }, { '$unwind': { 'path': '$weights' } }, { '$unwind': { 'path': '$weights.spotsAvailable' } }, { '$match': { 'weights.spotsAvailable.name': 'empty', 'weights.weight': 15 } }, { '$limit': 1 }, { '$set': { 'weights.spotsAvailable.name': 'Wayen', 'weights.spotsAvailable.userId': '123456' } } ] const client = await clientPromise; const db = client.db(); const query = db.collection('events').aggregate(agg); if (query) { console.log("we queried") console.log({ query }) return true } else { throw new Error("User not added to event") } } catch (e) { console.error(e); } } }
解决方案
核心问题
你之前用的aggregate()只是查询/数据转换操作,不会修改数据库里的文档,所以执行后看不到更新效果。要实现仅更新第一个匹配的嵌套子文档,应该用updateOne配合聚合管道作为更新操作(MongoDB 4.2及以上版本支持)。
实现代码
const acceptOrRemoveApplicant = async (eventId: ObjectId, createdBy: ObjectId, applicantId: ObjectId, applicantName: string, boolean: boolean, weight: number): Promise<boolean | undefined> => { console.log({ eventId, createdBy, applicantId, applicantName, boolean, weight }) if (boolean) { try { const client = await clientPromise; const db = client.db(); const result = await db.collection('events').updateOne( // 匹配目标事件文档 { _id: eventId, createdBy: createdBy, "weights.weight": weight }, // 用聚合管道做精准更新 [ { $set: { weights: { $map: { input: "$weights", as: "w", in: { $cond: [ { $eq: ["$$w.weight", weight] }, // 找到目标weight组,处理spotsAvailable { ..."$$w", spotsAvailable: { $concatArrays: [ // 取第一个empty条目之前的所有元素 { $slice: [ "$$w.spotsAvailable", { $indexOfArray: ["$$w.spotsAvailable.userId", "empty"] } ] }, // 替换第一个empty条目 [{ name: applicantName, userId: applicantId }], // 取第一个empty条目之后的所有元素 { $slice: [ "$$w.spotsAvailable", { $add: [{ $indexOfArray: ["$$w.spotsAvailable.userId", "empty"] }, 1] }, ] } ] } }, // 其他weight组保持原样 "$$w" ] } } } } } ] ); if (result.modifiedCount > 0) { console.log("成功更新第一个匹配的条目"); return true; } else { throw new Error("未找到可更新的条目"); } } catch (e) { console.error(e); return false; } } }
代码说明
- 用
$map遍历weights数组,定位到目标weight对应的分组 - 用
$indexOfArray找到该分组中第一个userId为"empty"的条目索引 - 用
$concatArrays将索引前的条目、替换后的条目、索引后的条目拼接,实现仅替换第一个匹配项 - 通过
updateOne执行更新,modifiedCount返回实际修改的文档数,用来判断更新是否成功
内容的提问来源于stack exchange,提问作者Wayne
相关产品推荐
相关产品推荐

