【问题标题】:MongoDB update nested array foreachMongoDB更新嵌套数组foreach
【发布时间】:2016-10-13 09:35:07
【问题描述】:

我有用户集合,每个用户都有一个数组祖先,以前的开发者做错了数据库架构,现在每个祖先都是 string 但必须是 ObjectId .它仍然包含 objectId(实际上是对象 ID 的 HEX,例如 558470744a73274db0f0d65d)。如何将每个祖先转换为 ObjectId?我是这样写的:

db.getCollection('Users').find({}).forEach(function(item){
  if (item.Ancestors instanceof Array){
      var tmp = new Array()
      item.Ancestors.forEach(function(ancestor){
          if (ancestor instanceof String){
               tmp.push(ObjectId(ancestor))
             }
          })
          item.Ancestors = tmp
          db.getCollection('Users').save(item) 
  }
})

但看起来它工作不正常,一些祖先现在是ObjectId,还有一些null。而且祖先也可以从一开始就为空。所以我把所有的if's

【问题讨论】:

    标签: arrays mongodb mongodb-query


    【解决方案1】:

    用猫鼬试试这个,

    var mongoose = require('mongoose');
    
    db.getCollection('Users').find({}).forEach(function(item){
      if (item.Ancestors instanceof Array){
          var tmp = new Array()
          item.Ancestors.forEach(function(ancestor){
              if (ancestor instanceof String){
                   tmp.push(mongoose.Types.ObjectId(ancestor))
                 }
              })
              item.Ancestors = tmp
              db.getCollection('Users').save(item) 
      }
    })
    

    【讨论】:

    • 谢谢! Mongo DB 运算符不适用于复杂的嵌套数组!您提到的这种方法更简单,更容易。
    【解决方案2】:

    这里的解决方案概念是使用游标遍历您的集合,并为游标内的每个文档收集有关Ancestors 数组元素的索引位置的数据。

    您稍后将在循环中将此数据用作更新操作参数,以正确识别要更新的元素。

    假设你的集合不是那么庞大,上面的直觉可以使用光标的 forEach() 方法来实现,就像你在尝试进行迭代并获取索引数据时所做的那样涉及的所有数组元素。

    以下演示了这种用于小型数据集的方法:

    function isValidHexStr(id) {
        var checkForHexRegExp = new RegExp("^[0-9a-fA-F]{24}$");
        if(id == null) return false;
        if(typeof id == "string") {
            return id.length == 12 || (id.length == 24 && checkForHexRegExp.test(id));
        }
        return false;
    };
    
    
    db.users.find({"Ancestors.0": { "$exists": true, "$type": 2 }}).forEach(function(doc){ 
        var ancestors = doc.Ancestors,
            updateOperatorDocument = {}; 
        for (var idx = 0; idx < ancestors.length; idx++){ 
            if(isValidHexStr(ancestors[idx]))                   
                updateOperatorDocument["Ancestors."+ idx] = ObjectId(ancestors[idx]);           
        };  
        db.users.updateOne(
            { "_id": doc._id },
            { "$set": updateOperatorDocument }
        );      
    });
    

    现在为了提高性能,尤其是在处理大型集合时,请利用 Bulk() API 批量更新集合。 与上述操作相比,这非常有效,因为使用 bulp API,您将分批将操作发送到服务器(例如,批量大小为 1000),这会给您带来更好的效果 性能,因为您不会向服务器发送每个请求,而是每 1000 个请求中发送一次,从而使您的更新更加高效和快捷。

    以下示例演示如何使用 MongoDB 版本 &gt;= 2.6&lt; 3.2 中提供的 Bulk() API。

    function isValidHexStr(id) {
        var checkForHexRegExp = new RegExp("^[0-9a-fA-F]{24}$");
        if(id == null) return false;
        if(typeof id == "string") {
            return id.length == 12 || (id.length == 24 && checkForHexRegExp.test(id));
        }
        return false;
    };
    
    var bulkUpdateOps = db.users.initializeUnorderedBulkOp(), 
        counter = 0;
    
    db.users.find({"Ancestors.0": { "$exists": true, "$type": 2 }}).forEach(function(doc){ 
        var ancestors = doc.Ancestors,
            updateOperatorDocument = {}; 
        for (var idx = 0; idx < ancestors.length; idx++){ 
            if(isValidHexStr(ancestors[idx]))                   
                updateOperatorDocument["Ancestors."+ idx] = ObjectId(ancestors[idx]);           
        };
        bulkUpdateOps.find({ "_id": doc._id }).update({ "$set": updateOperatorDocument })
    
        counter++;  // increment counter for batch limit
        if (counter % 1000 == 0) { 
            // execute the bulk update operation in batches of 1000
            bulkUpdateOps.execute(); 
            // Re-initialize the bulk update operations object
            bulkUpdateOps = db.users.initializeUnorderedBulkOp();
        } 
    })
    
    // Clean up remaining operation in the queue
    if (counter % 1000 != 0) { bulkUpdateOps.execute(); }
    

    下一个示例适用于新的 MongoDB 版本 3.2,它从 deprecated 开始使用 Bulk() API,并使用 bulkWrite() 提供了一组更新的 API。

    它使用与上面相同的游标,但使用相同的 forEach() 游标方法创建具有批量操作的数组,以将每个批量写入文档推送到数组。因为写入命令可以接受不超过 1000 次操作,所以您需要将操作分组为最多 1000 次操作,并在循环达到 1000 次迭代时重新初始化数组:

    var cursor = db.users.find({"Ancestors.0": { "$exists": true, "$type": 2 }}),
        bulkUpdateOps = [];
    
    cursor.forEach(function(doc){ 
        var ancestors = doc.Ancestors,
            updateOperatorDocument = {}; 
        for (var idx = 0; idx < ancestors.length; idx++){ 
            if(isValidHexStr(ancestors[idx]))                   
                updateOperatorDocument["Ancestors."+ idx] = ObjectId(ancestors[idx]);           
        };
        bulkUpdateOps.push({ 
            "updateOne": {
                "filter": { "_id": doc._id },
                "update": { "$set": updateOperatorDocument }
             }
        });
    
        if (bulkUpdateOps.length == 1000) {
            db.users.bulkWrite(bulkUpdateOps);
            bulkUpdateOps = [];
        }
    });         
    
    if (bulkUpdateOps.length > 0) { db.users.bulkWrite(bulkUpdateOps); }
    

    【讨论】:

    • 哇!谢谢!我听说过 Bulk 操作,但我想“让我们快点做吧”,而 Bulk 做的很快! 0.15 秒 250 场比赛!非常感谢!我将阅读有关批量的更多信息
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-22
    • 2017-09-11
    • 2019-12-07
    • 2018-10-22
    • 2019-06-08
    相关资源
    最近更新 更多