MongoDB преобразует строковый тип в тип с плавающей запятой

Следуя предложениям здесь MongoDB: как изменить тип поле? Я пытался обновить свою коллекцию, чтобы изменить тип поля и его значение.

Вот запрос на обновление

db.MyCollection.find({"ProjectID" : 44, "Cost": {$exists: true}}).forEach(function(doc){
    if(doc.Cost.length > 0){
        var newCost = doc.Cost.replace(/,/g, '').replace(/\$/g, '');
        doc.Cost =  parseFloat(newCost).toFixed(2);  
        db.MyCollection.save(doc);
        } // End of If Condition
    }) // End of foreach

после завершения вышеуказанного запроса, когда я запускаю следующую команду

db.MyCollection.find({"ProjectID" : 44},{Cost:1})

У меня все еще есть поле Cost в виде строки.

{
    "_id" : ObjectId("576919b66bab3bfcb9ff0915"),
    "Cost" : "11531.23"
}

/* 7 */
{
    "_id" : ObjectId("576919b66bab3bfcb9ff0916"),
    "Cost" : "13900.64"
}

/* 8 */
{
    "_id" : ObjectId("576919b66bab3bfcb9ff0917"),
    "Cost" : "15000.86"
}

Что я здесь делаю неправильно?

Вот образец документа

/* 2 */
{
    "_id" : ObjectId("576919b66bab3bfcb9ff0911"),
    "Cost" : "$7,100.00"
}

/* 3 */
{
    "_id" : ObjectId("576919b66bab3bfcb9ff0912"),
    "Cost" : "$14,500.00"
}

/* 4 */
{
    "_id" : ObjectId("576919b66bab3bfcb9ff0913"),
    "Cost" : "$12,619.00"
}

/* 5 */
{
    "_id" : ObjectId("576919b66bab3bfcb9ff0914"),
    "Cost" : "$9,250.00"
}

person HaBo    schedule 21.06.2016    source источник
comment
@chridam, пожалуйста, используйте этот {_id: ObjectId (576919b66bab3bfcb9ff0915), стоимость: $11 531,23}   -  person HaBo    schedule 21.06.2016
comment
@user3100115 обновлен образцом документа.   -  person HaBo    schedule 21.06.2016


Ответы (2)


Проблема в том, что toFixed возвращает String, а не Number. Тогда вы просто обновляете документ новым и другим String.

Пример из Mongo Shell:

> number = 2.3431
2.3431
> number.toFixed(2)
2.34
> typeof number.toFixed(2)
string

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

db.MyCollection.find({"ProjectID" : 44, "Cost": {$exists: true}}).forEach(function(doc){
  if(doc.Cost.length > 0){
    var newCost = doc.Cost.replace(/,/g, '').replace(/\$/g, '');
    var costString = parseFloat(newCost).toFixed(2);
    doc.Cost = parseFloat(costString);
    db.MyCollection.save(doc);
  } // End of If Condition
}) // End of foreach
person Enrique Fueyo    schedule 21.06.2016
comment
Спасибо за подробный ответ. - person HaBo; 21.06.2016

Следуйте этому шаблону, чтобы преобразовать поле валюты строкового типа в число с плавающей запятой. Вам нужно запросить все документы в коллекции, которые имеют строку типа поля Cost. Для этого вам необходимо воспользоваться преимуществами Bulk API для массовых обновлений. Они обеспечивают более высокую производительность, поскольку вы будете отправлять операции на сервер партиями, скажем, по 1000, что повышает производительность, поскольку вы отправляете не каждый запрос на сервер, а только один раз из каждых 1000 запросов.

Ниже демонстрируется этот подход. В первом примере используется Bulk API, доступный в версиях MongoDB >= 2.6 and < 3.2. Он обновляет все документы в коллекции, изменяя все поля Cost на поля с плавающими значениями:

var bulk = db.MyCollection.initializeUnorderedBulkOp(),
    counter = 0;

db.MyCollection.find({ 
    "Cost": { "$exists": true, "$type": 2 } 
}).forEach(function (doc) {
    var newCost = Number(doc.Cost.replace(/[^0-9\.]+/g,"")); 
    bulk.find({ "_id": doc._id }).updateOne({ 
        "$set": { "Cost": newCost }
    });

    counter++;
    if (counter % 1000 == 0) {
        bulk.execute(); // Execute per 1000 operations 
        // re-initialize every 1000 update statements
        bulk = db.MyCollection.initializeUnorderedBulkOp(); 
    }
})
// Clean up remaining operations in queue
if (counter % 1000 != 0) { bulk.execute(); }

Следующий пример относится к новой версии MongoDB 3.2, которая с тех пор имеет устарела Bulk API< /a> и предоставил более новый набор API, используя bulkWrite().

Он использует те же курсоры, что и выше, но создает массивы с массовыми операциями, используя тот же метод курсора forEach(), чтобы помещать каждый документ массовой записи в массив. Поскольку команды записи могут принимать не более 1000 операций, вам нужно будет сгруппировать свои операции, чтобы иметь не более 1000 операций, и повторно инициализировать массив, когда цикл достигает 1000 итераций:

var cursor = db.MyCollection.find({ "Cost": { "$exists": true, "$type": 2 } }),
    bulkUpdateOps = [];

cursor.forEach(function(doc){ 
    var newCost = Number(doc.Cost.replace(/[^0-9\.]+/g,""));
    bulkUpdateOps.push({ 
        "updateOne": {
            "filter": { "_id": doc._id },
            "update": { "$set": { "Cost": newCost } }
         }
    });

    if (bulkUpdateOps.length == 1000) {
        db.MyCollection.bulkWrite(bulkUpdateOps);
        bulkUpdateOps = [];
    }
});         

if (bulkUpdateOps.length > 0) { db.MyCollection.bulkWrite(bulkUpdateOps); }
person chridam    schedule 21.06.2016
comment
будет ли Number(...) содержать десятичные значения? - person HaBo; 21.06.2016
comment
Если я могу спросить, каково значение $type: 2 - person HaBo; 21.06.2016
comment
Это действительно потрясающая информация .. Спасибо - person HaBo; 21.06.2016
comment
Это лучший ответ — выполнение этого запроса на любом значительном наборе данных (> 20 000 документов), скорее всего, не удастся. Использование BulkOps — правильный путь. - person John O'Connor; 07.08.2017