#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 не подходят для сложных вложенных массивов! Этот метод, о котором вы упомянули, намного проще и проще.