使用PyMongo向MongoDB嵌套数组指定元素推送内容的问题
看起来你是想精准定位到courses数组里某个特定课程,给它的course_content数组添加新元素对吧?咱们一步步来理清楚问题出在哪,以及正确的写法应该是怎样的:
首先分析你最初的错误写法
你之前的update_one调用把查询条件放错了位置——PyMongo的update_one方法参数顺序是:查询过滤器、更新操作、可选配置(比如upsert、arrayFilters等)。你原本的代码:
collection.update_one( {'_id': ObjectId(session_document_id)}, {'courses': {'$elemMatch': {'course_name': 'great course'}}}, {'$push': {'courses.$.course_name':{summary: "the quick brown fox", info:"this is from a particular source" }}} )
这里第二个参数应该是$push这类更新操作,而不是查询条件;另外courses.$.course_name也写错了,目标字段应该是courses.$.course_content才对。
再看你用arrayFilters时的问题
你尝试arrayFilters的时候,把配置参数传错了位置,arrayFilters和upsert应该放在第三个参数的字典里,而不是混进更新操作里。正确的arrayFilters写法应该是这样:
collection.update_one( {'_id': ObjectId(session_document_id), "courses.course_name": course_name}, {"$push": {"courses.$[course].course_content": content}}, {"arrayFilters": [{"course.course_name": course_name}], "upsert": False} )
注意这里arrayFilters里的变量名course要和更新操作里的$[course]对应上,这样才能精准匹配到目标课程元素。
为什么$[]会更新所有元素?
你用courses.$[].course_content的时候,$[]是MongoDB的所有数组元素占位符,它会匹配courses数组里的每一个元素,所以自然会把内容推送到所有课程的course_content里,这显然不是你想要的效果。
最终可行的写法(你找到的正确方案)
你最后用的find_one_and_update写法是完全正确的,这里的$占位符会匹配查询条件里courses.course_name对应的那个数组元素,精准定位到目标课程,然后给它的course_content推送新内容:
collection.find_one_and_update( filter={'_id': ObjectId('6576576759045839397565bd'), "courses.course_name": course_name}, update={"$push": {"courses.$.course_content": content}}, upsert=True )
如果目标课程还没有course_content字段,MongoDB会自动帮你创建这个数组并把内容加进去;如果已经有了,就会把新元素追加到数组末尾,完全符合你的需求。
另外补充一点:如果用update_one的话,对应的正确写法也是类似的,只是返回的结果是更新的状态,而find_one_and_update会返回更新前或更新后的文档(可以通过return_document参数控制):
collection.update_one( {'_id': ObjectId('6576576759045839397565bd'), "courses.course_name": course_name}, {"$push": {"courses.$.course_content": content}}, upsert=True )
备注:内容来源于stack exchange,提问作者DiscoStu

