使用pymongo操作MongoDB嵌套文档 实现城市数组全量覆盖更新问题
问题解决方案
错误原因说明
- 原有步骤2的
$addToSet逻辑缺陷:$addToSet判断数组元素是否重复的依据是整个对象完全一致,首次插入国家后该对象会新增cities字段,后续再传入仅包含country_id和country_name的对象时,$addToSet会判定为新对象,导致重复插入同id的国家。 - 原有第三步的写法直接对
countries根级数组赋值,会覆盖整个数组的所有内容,自然丢失其他国家的信息。
实现方案
方案1:兼容性写法(支持所有MongoDB版本)
分两步操作,逻辑清晰无歧义:
- 先确保大洲文档存在,仅插入新文档时赋值,不会覆盖已有数据
continents.update_one( {"continent_id": CONTINENT_ID}, {"$setOnInsert": {"continent_name": CONTINENT_NAME, "countries": []}}, upsert=True )
- 原子更新国家城市数据:先尝试覆盖已有国家的城市数组,匹配不到则新增带城市的国家条目
# 尝试更新已有国家的城市数组 update_result = continents.update_one( { "continent_id": CONTINENT_ID, "countries.country_id": COUNTRY_ID }, { "$set": { "countries.$[country].cities": [{"city_id": city["city_id"], "city_name": city["city_name"]} for city in CITIES] } }, array_filters=[{"country.country_id": COUNTRY_ID}] ) # 没有匹配到说明国家不存在,直接追加新国家+城市数据 if update_result.matched_count == 0: continents.update_one( {"continent_id": CONTINENT_ID}, { "$push": { "countries": { "country_id": COUNTRY_ID, "country_name": COUNTRY_NAME, "cities": [{"city_id": city["city_id"], "city_name": city["city_name"]} for city in CITIES] } } } )
方案2:单语句管道写法(MongoDB 4.2+支持)
用更新管道单条语句完成所有逻辑,无需判断返回结果:
continents.update_one( {"continent_id": CONTINENT_ID}, [ # 过滤掉countries数组中当前国家的旧数据 { "$set": { "countries": { "$filter": { "input": "$countries", "cond": {"$ne": ["$$this.country_id", COUNTRY_ID]} } } } }, # 插入当前国家的最新全量数据 { "$push": { "countries": { "country_id": COUNTRY_ID, "country_name": COUNTRY_NAME, "cities": [{"city_id": city["city_id"], "city_name": city["city_name"]} for city in CITIES] } } } ], upsert=True )
两种方案都完全符合需求:国家不存在时新增带城市数组的条目,国家存在时全量覆盖原有城市数组,不会保留旧数据,也不会出现重复国家的问题。
内容的提问来源于stack exchange,提问作者Jake Ratcliffe
相关产品推荐
相关产品推荐

