如何用$set和点表示法将MongoDB嵌入式数组旧值更新到对应元素新字段
我在MongoDB中有以下文档,使用PyMongo初始化的代码如下:
from pymongo import MongoClient client = MongoClient(host='my_host', port=27017) database = client.forecast collection = database.regions collection.delete_many({}) regions = [ { 'id': 'DE', 'sites': [ { 'name': 'paper_factory', 'energy_consumption': 1000 }, { 'name': 'chair_factory', 'energy_consumption': 2000 }, ] }, { 'id': 'FR', 'sites': [ { 'name': 'pizza_factory', 'energy_consumption': 3000 }, { 'name': 'foo_factory', 'energy_consumption': 4000 }, ] } ] collection.insert_many(regions)
现在我需要为每个site元素,将sites.energy_consumption属性值复制到新字段sites.new_field中,我编写的聚合操作代码如下:
set_stage = { "$set": { "sites.new_field": "$sites.energy_consumption" } } pipeline = [set_stage] collection.aggregate(pipeline)
但执行后并未实现预期效果:没有为每个site复制对应的单个energy_consumption值,而是将整个sites数组的所有energy_consumption值收集为数组,赋值给了每个site的new_field。例如DE地区的第一个site预期得到'new_field': 1000,实际得到的是'new_field': [1000, 2000],错误的返回结果示例如下:
{ "_id": ObjectId("61600c11732a5d6b103ba6be"), "id": "DE", "sites": [ { "name": "paper_factory", "energy_consumption": 1000, "new_field": [ 1000, 2000 ] }, { "name": "chair_factory", "energy_consumption": 2000, "new_field": [ 1000, 2000 ] } ] }, { "_id": ObjectId("61600c11732a5d6b103ba6bf"), "id": "FR", "sites": [ { "name": "pizza_factory", "energy_consumption": 3000, "new_field": [ 3000, 4000 ] }, { "name": "foo_factory", "energy_consumption": 4000, "new_field": [ 3000, 4000 ] } ] }
我需要使用什么表达式才能仅获取数组中对应位置的元素值?
是否存在类似当前索引运算符的语法:
$sites[<current_index>].energy_consumption
或者替代点运算符(类似矩阵运算中普通乘法*与逐元素乘法.*的区别),例如:
$sites:energy_consumption
还是说该问题属于MongoDB的bug?
补充说明
我也尝试过使用"$" 位置运算符,例如使用sites.$.new_field或者$sites.$.energy_consumption的写法,但执行后返回报错:
FieldPath field names may not start with '$'
这不是MongoDB的bug,属于对数组字段聚合操作的用法理解偏差:直接使用$sites.energy_consumption时,MongoDB会提取整个sites数组中所有元素的energy_consumption值组成新数组,因此会出现每个site的new_field都被赋值为全量数组的问题。
正确方案是使用$map运算符遍历sites数组,对每个元素单独处理:
set_stage = { "$set": { "sites": { "$map": { "input": "$sites", "as": "site", "in": { "$mergeObjects": [ "$$site", {"new_field": "$$site.energy_consumption"} ] } } } } } pipeline = [set_stage] collection.aggregate(pipeline)
$map会逐个遍历sites数组的元素,将当前遍历的元素赋值给临时变量site,再通过$mergeObjects将原site字段内容和新增的new_field字段合并,最后将处理后的新数组覆盖原sites字段,即可实现每个site的new_field为自身energy_consumption值的效果。
你之前使用位置运算符报错的原因是:普通更新操作的位置运算符$不适用于聚合管道的$set阶段,因此会触发字段名不允许以$开头的语法错误。
内容的提问来源于stack exchange,提问作者Stefan

