MongoDB
 sql >> Database >  >> NoSQL >> MongoDB

La query aggregata mongodb non restituisce la somma corretta utilizzando $sum

Il tuo schema attuale ha i marks tipo di dati del campo come stringa e hai bisogno di un tipo di dati intero per il tuo framework di aggregazione per calcolare la somma. D'altra parte, puoi usare MapReduce per calcolare la somma poiché consente l'uso di metodi JavaScript nativi come parseInt() sulle proprietà dell'oggetto nelle sue funzioni di mappa. Quindi nel complesso hai due scelte.

Opzione 1:Aggiorna schema (cambia tipo di dati)

Il primo sarebbe modificare lo schema o aggiungere un altro campo nel documento che abbia il valore numerico effettivo e non la rappresentazione della stringa. Se le dimensioni del tuo documento di raccolta sono relativamente piccole, puoi utilizzare una combinazione del cursore di mongodb find() , forEach() e update() metodi per modificare lo schema dei voti:

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

Per raccolte di dimensioni relativamente grandi, le prestazioni del tuo db saranno lente e si consiglia di utilizzare aggiornamenti collettivi mongo per questo:

Versioni 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 versione 3.2 e successive:

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);

Opzione 2:esegui MapReduce

Il secondo approccio sarebbe riscrivere la query con MapReduce dove puoi usare la funzione JavaScript parseInt() .

Nel tuo MapReduce operazione, definire la funzione mappa che elabora ogni documento di input. Questa funzione mappa i marks convertiti valore stringa al subject per ogni documento ed emette il subject e convertito marks coppia. Qui è dove la funzione nativa JavaScript parseInt() può essere applicato. Nota:nella funzione, this si riferisce al documento che sta elaborando l'operazione di riduzione della mappa:

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

Quindi, definisci la corrispondente funzione di riduzione con due argomenti keySubject e valuesMarks . valuesMarks è un array i cui elementi sono gli interi marks valori emessi dalla funzione map e raggruppati per keySubject .La funzione riduce i valuesMarks array alla somma dei suoi elementi.

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

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

Con la tua raccolta, quanto sopra inserirà il risultato dell'aggregazione MapReduce in una nuova raccolta db.example_results . Pertanto, db.example_results.find() produrrà:

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