MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

A consulta agregada do mongodb não está retornando a soma adequada ao usar $sum


Seu esquema atual tem as marks tipo de dados de campo como string e você precisa de um tipo de dados inteiro para sua estrutura de agregação para calcular a soma. Por outro lado, você pode usar MapReduce para calcular a soma, pois permite o uso de métodos JavaScript nativos como parseInt() nas propriedades do seu objeto em suas funções de mapa. Então, no geral, você tem duas opções.

Opção 1:atualizar esquema (alterar tipo de dados)


A primeira seria alterar o esquema ou adicionar outro campo em seu documento que tenha o valor numérico real e não a representação da string. Se o tamanho do seu documento de coleção for relativamente pequeno, você pode usar uma combinação do cursor do mongodb find() , forEach() e update() métodos para alterar seu 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 tamanhos de coleção relativamente grandes, o desempenho do banco de dados será lento e é recomendável usar atualizações em massa do mongo por esta:

Versões do MongoDB>=2.6 e <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 versão 3.2 e mais recente:
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);

Opção 2:execute o MapReduce


A segunda abordagem seria reescrever sua consulta com MapReduce onde você pode usar a função JavaScript parseInt() .

Em seu MapReduce operação, defina a função map que processa cada documento de entrada. Esta função mapeia as marks convertidas valor da string para o subject para cada documento e emite o subject e converteu marks par. É aqui que a função nativa JavaScript parseInt() pode ser aplicado. Nota:na função, this refere-se ao documento que a operação map-reduce está processando:
var mapper = function () {
    var x = parseInt(this.marks);
    emit(this.subject, x);
};

Em seguida, defina a função de redução correspondente com dois argumentos keySubject e valuesMarks . valuesMarks é uma matriz cujos elementos são o inteiro marks valores emitidos pela função map e agrupados por keySubject .A função reduz os valuesMarks array para a soma de seus elementos.
var reducer = function(keySubject, valuesMarks) {
    return Array.sum(valuesMarks);
};

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

Com sua coleção, o acima colocará o resultado da agregação do MapReduce em uma nova coleção db.example_results . Assim, db.example_results.find() emitirá:
/* 0 */
{
    "_id" : "maths",
    "value" : 163
}