MongoDB обновляет вложенный массив foreach

#arrays #mongodb #mongodb-query

#массивы #mongodb #mongodb-запрос

Вопрос:

У меня есть коллекция пользователей, и у каждого пользователя есть массив предков, предыдущий разработчик сделал неправильную архитектуру БД, и теперь каждый из предков является строкой, но должен быть идентификатором объекта. Он по-прежнему содержит ObjectId (фактически шестнадцатеричный идентификатор объекта, например 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

Ответ №1:

Концепция решения здесь заключается в том, чтобы перебирать вашу коллекцию с помощью курсора и для каждого документа внутри курсора собирать данные о позиции индекса 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 amp;amp; 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запросы, что делает ваши обновления более эффективными и быстрыми.

Следующие примеры демонстрируют использование Bulk() API, доступного в версиях MongoDB >= 2.6 и < 3.2 .

 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 amp;amp; 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, которая с тех пор устарела от Bulk() API и предоставила более новый набор API с использованием bulkWrite() .

Он использует те же курсоры, что и выше, но создает массивы с помощью массовых операций, используя тот же 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); }
  

Комментарии:

1. Вау! Спасибо! Я слышал о массовых операциях, но подумал: «Давайте сделаем это быстро», и Bulk сделал это быстро! 0,15 сек за 250 совпадений! Большое спасибо! Я прочитаю больше о bulk

Ответ №2:

Попробуйте сделать это с помощью mongoose,

 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) 
  }
})
  

Комментарии:

1. Спасибо! Операторы Mongo DB не подходят для сложных вложенных массивов! Этот метод, о котором вы упомянули, намного проще и проще.