Самый эффективный способ изменить значение поля строки в подстроке

У меня есть коллекция, заполненная документами, которые выглядят так:

{
 data: 11,
 version: "0.0.32" 
}

а некоторые имеют суффикс test до version:

{
 data: 55,
 version: "0.0.42-test" 
}

Поле version имеет разные значения, но оно всегда соответствует шаблону: 0.0.XXX. Я хотел бы обновить все документы, чтобы они выглядели следующим образом:

{
 data: 11,
 version: 32 
}

а суффиксная версия (для тестовых документов - version должна быть отрицательной):

{
 data: 55,
 version: -42 
}

Сбор с этими документами используется нашей критической системой, которую необходимо отключить при обновлении данных, поэтому я хочу, чтобы обновление/изменение было как можно быстрее. В этой коллекции есть около 66_000_000 документов, размер около 100 ГБ.

Какой тип операции mongodb был бы наиболее эффективным?

1 ответ

Самый эффективный способ сделать это - в предстоящей версии MongoDB на момент написания этой статьи с помощью оператора $split для разделения нашей строки как показанной здесь, затем назначить последний элемент в массиве переменной, используя переменный оператор $let и $arrayElemAt.

Затем мы используем оператор $switch для выполнения обработки логического условия или оператора case с этой переменной.

Условие здесь $gt, которое возвращает true, если значение содержит "test", и в этом случае в выражении in мы разделяем эту строку и просто возвращаем значение $concat в значение первого элемента в новом вычисляемом массиве и -. Если условие принимает значение false, мы просто возвращаем переменную.

Конечно, в нашем случае мы используем $indexOfCP, который возвращает -1, если не было вхождений "test".

let cursor = db.collection.aggregate(
 [
 { "$project": { 
 "data": 1, 
 "version": { 
 "$let": { 
 "vars": { 
 "v": { 
 "$arrayElemAt": [
 { "$split": [ "$version", "." ] }, 
 -1
 ]
 }
 }, 
 "in": { 
 "$switch": { 
 "branches": [ 
 { 
 "case": { 
 "$gt": [ 
 { "$indexOfCP": [ "$$v", "test" ] },
 -1 
 ]
 }, 
 "then": { 
 "$concat": [ 
 "-", 
 "", 
 { "$arrayElemAt": [
 { "$split": [ "$$v", "-" ] }, 
 0 
 ]} 
 ]
 }
 }
 ], 
 "default": "$$v" 
 }
 }
 }
 }
 }}
 ]
)

Запрос агрегации создает примерно следующее:

{ "_id" : ObjectId("57a98773cbbd42a2156260d8"), "data" : 11, "version" : "32" }
{ "_id" : ObjectId("57a98773cbbd42a2156260d9"), "data" : 55, "version" : "-42" }

Как вы можете видеть, полевые данные "версия" являются строковыми. Если тип данных для этого поля не имеет значения, вы можете просто использовать оператор этапа сценария агрегации $out для записи результата в новую коллекцию или заменить свою коллекцию.

{ "out": "collection" }

Если вам нужно преобразовать ваши данные в число с плавающей запятой, тогда единственный способ сделать это просто потому, что MongoDB не предоставляет способ сделать преобразование типа из коробки, за исключением целого числа в строку, - это итерация агрегирование объекта Cursor и преобразование вашего значения с помощью parseFloat или Number, обновите свои документы с помощью оператора $set и bulkWrite() для максимальной эффективности.

let requests = [];
cursor.forEach(doc => { 
 requests.push({ 
 "updateOne": { 
 "filter": { "_id": doc._id }, 
 "update": { 
 "$set": { 
 "data": doc.data, 
 "version": parseFloat(doc.version) 
 },
 "$unset": { "person": " " }
 } 
 } 
 }); 
 if ( requests.length === 1000 ) { 
 // Execute per 1000 ops and re-init
 db.collection.bulkWrite(requests); 
 requests = []; 
 }} 
);
 // Clean up queues
if(requests.length > 0) {
 db.coll.bulkWrite(requests);
}

В то время как запрос агрегации отлично работает в MongoDB 3.4 или новее, наша лучшая ставка от MongoDB 3.2 назад mapReduce с помощью bulkWrite().

var results = db.collection.mapReduce(
 function() { 
 var v = this.version.split(".")[2]; 
 emit(this._id, v.indexOf("-") > -1 ? "-"+v.replace(/\D+/g, '') : v)
 }, 
 function(key, value) {}, 
 { "out": { "inline": 1 } }
)["results"];

results выглядит следующим образом:

[
 {
 "_id" : ObjectId("57a98773cbbd42a2156260d8"),
 "value" : "32"
 },
 {
 "_id" : ObjectId("57a98773cbbd42a2156260d9"),
 "value" : "-42"
 }
]

Здесь вы используете предыдущий цикл .forEach для обновления ваших документов.

От MongoDB 2.6 до 3.0 вам нужно будет использовать теперь устаревший Bulk() API и связанный с ним метод, как показано в моем здесь.

licensed under cc by-sa 3.0 with attribution.