MongoDB单查询批量更新内嵌数组及外部变量调用方法
MongoDB批量更新与外部变量调用问题
集合结构
cities集合中存在如下文档:
{ "name": "London", "suburbs": [ { "_id": ObjectId("..."), "city_id": 0, "name": "Chelsea", "population": "41k" }, { "_id": ObjectId("..."), "city_id": 1, "name": "Fulham", "population": "87k" }, { "_id": ObjectId("..."), "city_id": 2, "name": "Kensington", "population": "64k" } ] }
待添加的外部数据
需基于以下JavaScript数组,为suburbs数组中的元素添加team和division字段:
let cities = [ { "team": "Chelsea FC", "division": "premier league", "id": 0 }, { "team": "Fulham FC", "division": "premier league", "id": 1 }, { "team": "Kensington FC", "division": "CCFL", "id": 2 } ]
当前实现方式
目前通过循环调用updateOne完成更新:
for(let o = 0; o < cities.length; o++) { db.cities.updateOne( { "name": "London", "suburbs.city_id": cities[o].id }, { $set: { "suburbs.$.team": cities[o].team, "suburbs.$.division": cities[o].division } } ) }
问题解答
1. 可以通过单条查询完成批量更新
在MongoDB 4.2及以上版本中,支持使用聚合管道作为更新操作的参数,结合$map和$mergeObjects可以一次性完成数组元素的批量字段添加,提供两种实现方式:
方式一:利用数组过滤匹配
直接在聚合管道中通过$filter匹配外部数组的对应元素,合并到原suburbs项中:
db.cities.updateMany( { "name": "London" }, // 匹配目标文档 [ { $set: { suburbs: { $map: { input: "$suburbs", as: "sub", in: { $mergeObjects: [ "$$sub", // 保留原郊区的所有字段 // 从外部数组中匹配对应city_id的team和division { $first: { $filter: { input: cities, cond: { $eq: ["$$this.id", "$$sub.city_id"] } } } } ] } } } } } ] );
方式二:提前转换为映射对象(性能更优)
先把外部数组转成以id为键的映射对象,再在管道中快速匹配:
// 转换外部数组为映射对象,便于快速查找 const teamMapping = cities.reduce((map, item) => { map[item.id] = { team: item.team, division: item.division }; return map; }, {}); db.cities.updateMany( { "name": "London" }, [ { $set: { suburbs: { $map: { input: "$suburbs", as: "sub", in: { $mergeObjects: [ "$$sub", // 根据city_id直接从映射对象中取对应值 teamMapping["$$sub.city_id"] || {} ] } } } } } ] );
2. MongoDB查询中调用外部变量的方法
根据使用场景,有两种常见方式:
方式一:直接引用(Mongo Shell/Node.js驱动环境)
在Mongo Shell或者Node.js的Mongo驱动代码中,外部定义的JavaScript变量可以直接在查询或聚合管道中使用,比如上述代码中直接引用cities数组或者teamMapping对象。
方式二:通过let参数传递(正式场景推荐)
MongoDB 3.6及以上版本支持通过let参数将外部变量传递到聚合管道中,避免变量名冲突,可读性更强:
db.cities.updateMany( { "name": "London" }, [ { $set: { suburbs: { $map: { input: "$suburbs", as: "sub", in: { $mergeObjects: [ "$$sub", { $first: { $filter: { input: "$$externalTeams", // 引用传递进来的变量 cond: { $eq: ["$$this.id", "$$sub.city_id"] } } } } ] } } } } } ], { let: { externalTeams: cities } // 传递外部变量到管道中 } );
内容的提问来源于stack exchange,提问作者KeeperOfTheSevenKeys
相关产品推荐
相关产品推荐

