sql >> Base de Datos >  >> NoSQL >> MongoDB

La consulta agregada de mongodb no devuelve la suma adecuada al usar $sum

Su esquema actual tiene las marks tipo de datos de campo como cadena y necesita un tipo de datos entero para que su marco de agregación calcule la suma. Por otro lado, puedes usar MapReduce para calcular la suma ya que permite el uso de métodos JavaScript nativos como parseInt() en las propiedades de su objeto en sus funciones de mapa. Entonces, en general, tienes dos opciones.

Opción 1:Actualizar esquema (cambiar tipo de datos)

La primera sería cambiar el esquema o agregar otro campo en su documento que tenga el valor numérico real, no la representación de cadena. Si el tamaño del documento de su colección es relativamente pequeño, puede usar una combinación del cursor de mongodb find() , forEach() y update() métodos para cambiar su esquema de marcas:

db.student.find({ "marks": { "$type": 2 } }).snapshot().forEach(function(doc) {
    db.student.update(
        { "_id": doc._id, "marks": { "$type": 2 } }, 
        { "$set": { "marks": parseInt(doc.marks) } }
    );
});

Para tamaños de colección relativamente grandes, el rendimiento de su base de datos será lento y se recomienda usar actualizaciones masivas de mongo por esto:

Versiones de MongoDB>=2.6 y <3.2:

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

db.student.find({"marks": {"$exists": true, "$type": 2 }}).forEach(function (doc) {    
    bulk.find({ "_id": doc._id }).updateOne({ 
        "$set": { "marks": parseInt(doc.marks) } 
    });

    counter++;
    if (counter % 1000 === 0) {
        // Execute per 1000 operations 
        bulk.execute(); 

        // re-initialize every 1000 update statements
        bulk = db.student.initializeUnorderedBulkOp();
    }
})

// Clean up remaining operations in queue
if (counter % 1000 !== 0) bulk.execute(); 

MongoDB versión 3.2 y posteriores:

var ops = [],
    cursor = db.student.find({"marks": {"$exists": true, "$type": 2 }});

cursor.forEach(function (doc) {     
    ops.push({ 
        "updateOne": { 
            "filter": { "_id": doc._id } ,              
            "update": { "$set": { "marks": parseInt(doc.marks) } } 
        }         
    });

    if (ops.length === 1000) {
        db.student.bulkWrite(ops);
        ops = [];
    }     
});

if (ops.length > 0) db.student.bulkWrite(ops);

Opción 2:Ejecutar MapReduce

El segundo enfoque sería volver a escribir su consulta con MapReduce donde puede usar la función JavaScript parseInt() .

En tu MapReduce operación, defina la función de mapa que procesa cada documento de entrada. Esta función asigna las marks convertidas valor de cadena al subject para cada documento, y emite el subject y marks convertidas par. Aquí es donde la función nativa de JavaScript parseInt() puede ser aplicado. Nota:en la función, this se refiere al documento que está procesando la operación map-reduce:

var mapper = function () {
    var x = parseInt(this.marks);
    emit(this.subject, x);
};

A continuación, defina la función de reducción correspondiente con dos argumentos keySubject y valuesMarks . valuesMarks es una matriz cuyos elementos son los enteros marks valores emitidos por la función map y agrupados por keySubject .La función reduce los valuesMarks arreglo a la suma de sus elementos.

var reducer = function(keySubject, valuesMarks) {
    return Array.sum(valuesMarks);
};

db.student.mapReduce(
    mapper,
    reducer,
    {
        out : "example_results",
        query: { subject : "maths" }       
    }
 );

Con su colección, lo anterior pondrá su resultado de agregación de MapReduce en una nueva colección db.example_results . Por lo tanto, db.example_results.find() generará:

/* 0 */
{
    "_id" : "maths",
    "value" : 163
}