MongoDB:使用匹配该项的另一个字段更新数组中项目的字段

4
我有这样一种数据结构:
我们有一些“中心”。一个“中心”有一些“交换机”。一个“交换机”有一些“端口”。
    {
     "_id" : ObjectId("561ad881755a021904c00fb5"),
     "Name" : "center1",
     "Switches" : [
        {
            "Ports" : [
                {
                    "PortNumber" : 2,
                    "Status" : "Empty"
                },
                {
                    "PortNumber" : 5,
                    "Status" : "Used"
                },
                {
                    "PortNumber" : 7,
                    "Status" : "Used"
                }
            ]
        }
     ]
  }

我只想编写一个更新查询来更改端口的状态,使其PortNumber为5时变为“空”。


当我知道端口的数组索引(这里数组索引为1)时,我可以使用以下查询进行更新:

db.colection.update(
    // query
    {
        _id: ObjectId("561ad881755a021904c00fb5")
    },
    // update 
    {
        $set : { "Switches.0.Ports.1.Status" : "Empty" }
    }
);

但我不知道那个端口的数组索引。
谢谢帮忙。

2个回答

6
您通常会使用定位操作符$来实现这一点,详见以下问题的答案: 在MongoDB中更新确切元素数组中的字段 不幸的是,目前定位操作符仅支持匹配一个数组层级。
对于您想要的行为,有一个JIRA票据: https://jira.mongodb.org/browse/SERVER-831 如果可以将Switches转换为对象,则可以执行以下操作:
db.colection.update(
    {
        _id: ObjectId("561ad881755a021904c00fb5"),
        "Switch.Ports.PortNumber": 5
    }, 
    {
        $set: {
            "Switch.Ports.$.Status": "Empty"
        }
    }
)

谢谢您的回答。看来我需要将我的数据结构展开成多个集合。 - Aliaaa

1
由于您不知道端口的数组索引,建议您动态创建$set条件,即一些能帮助您获取对象索引并相应修改的东西,然后考虑使用MapReduce。目前似乎无法使用聚合框架实现此功能。有一个未解决的开放性JIRA问题与之相关联。但是,可以通过MapReduce实现解决方法。MapReduce的基本思想是它使用JavaScript作为其查询语言,但这往往比聚合框架慢得多,不应用于实时数据分析。
在MapReduce操作中,您需要定义几个步骤,即映射步骤(将操作映射到集合中的每个文档,操作可以什么也不做或者发出具有键和投影值的某些对象)和归约步骤(将发出的值列表归约为单个元素)。
对于映射步骤,理想情况下您希望为集合中的每个文档获取SwitchesPorts数组字段的索引以及包含$set键的另一个键。
您的缩减步骤将是一个函数(什么也不做),简单定义为var reduce = function() {}; 然后,MapReduce操作的最后一步将创建一个单独的Switches集合,其中包含发出的Switches数组对象以及一个带有$set条件的字段。当您在原始集合上运行MapReduce操作时,可以定期更新此集合。
总体而言,这个MapReduce方法看起来像:
var map = function(){
    for(var i = 0; i < this.Switches.length; i++){
        for(var j = 0; j < this.Switches[i].Ports.length; j++){
            emit( 
                {
                    "_id": this._id, 
                    "switch_index": i, 
                    "port_index": j 
                }, 
                {
                    "index": j, 
                    "Switches": this.Switches[i],  
                    "Port": this.Switches[i].Ports[j],                      
                    "update": {
                        "PortNumber": "Switches." + i.toString() + ".Ports." + j.toString() + ".PortNumber",
                        "Status": "Switches." + i.toString() + ".Ports." + j.toString() + ".Status"
                    }                    
                }
            );
        }            
    }
};

var reduce = function(){};

db.centers.mapReduce(
    map,
    reduce,
    {
        "out": {
            "replace": "switches"
        }
    }
);

从MapReduce操作查询输出集合Switches通常会给出以下结果:

db.switches.findOne()

样例输出:

{
    "_id" : {
        "_id" : ObjectId("561ad881755a021904c00fb5"),
        "switch_index" : 0,
        "port_index" : 1
    },
    "value" : {
        "index" : 1,
        "Switches" : {
            "Ports" : [ 
                {
                    "PortNumber" : 2,
                    "Status" : "Empty"
                }, 
                {
                    "PortNumber" : 5,
                    "Status" : "Used"
                }, 
                {
                    "PortNumber" : 7,
                    "Status" : "Used"
                }
            ]
        },
        "Port" : {
            "PortNumber" : 5,
            "Status" : "Used"
        },
        "update" : {
            "PortNumber" : "Switches.0.Ports.1.PortNumber",
            "Status" : "Switches.0.Ports.1.Status"
        }
    }
}

您可以使用来自db.switches.find()方法的光标来迭代并相应地更新您的集合:
var newStatus = "Empty";
var cur = db.switches.find({ "value.Port.PortNumber": 5 });     

// Iterate through results and update using the update query object set dynamically by using the array-index syntax.
while (cur.hasNext()) {
    var doc = cur.next();
    var update = { "$set": {} };
    // set the update query object
    update["$set"][doc.value.update.Status] = newStatus;

    db.centers.update(
        {
            "_id": doc._id._id, 
            "Switches.Ports.PortNumber": 5
        }, 
        update 
    );      
};

1
感谢您的出色解释。我认为在我的项目情况下,对我来说最好的解决方案是扁平化数据结构。但是您的解决方案也是完整的,并且适用于某些情况。 - Aliaaa
@Aliaaa 不用担心。是的,我同意将数据扁平化可以在解决大多数复杂的深度嵌套数组更新方面发挥作用,这是MongoDB目前难以处理的原子操作。 - chridam
现在这个问题已经被修复。请使用arrayFilters,查看https://jira.mongodb.org/browse/SERVER-831。 - Shyam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接