MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

La requête globale mongodb ne renvoie pas la somme appropriée lors de l'utilisation de $sum

Votre schéma actuel a les marks type de données de champ sous forme de chaîne et vous avez besoin d'un type de données entier pour votre cadre d'agrégation pour calculer la somme. D'autre part, vous pouvez utiliser MapReduce pour calculer la somme car cela permet l'utilisation de méthodes JavaScript natives comme parseInt() sur les propriétés de votre objet dans ses fonctions cartographiques. Donc, globalement, vous avez deux choix.

Option 1 :Mettre à jour le schéma (modifier le type de données)

La première serait de changer le schéma ou d'ajouter un autre champ dans votre document qui a la valeur numérique réelle et non la représentation sous forme de chaîne. Si la taille de votre document de collection est relativement petite, vous pouvez utiliser une combinaison du curseur de mongodb find() , forEach() et update() méthodes pour modifier votre schéma de repères :

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

Pour des tailles de collection relativement importantes, les performances de votre base de données seront lentes et il est recommandé d'utiliser  mises à jour groupées de mongo pour cela :

Versions MongoDB>=2.6 et <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 version 3.2 et versions ultérieures :

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

Option 2 :Exécuter MapReduce

La deuxième approche serait de réécrire votre requête avec MapReduce où vous pouvez utiliser la fonction JavaScript parseInt() .

Dans votre MapReduce opération, définissez la fonction de carte qui traite chaque document d'entrée. Cette fonction mappe les marks converties valeur de chaîne au subject pour chaque document, et émet le subject et converti les marks paire. C'est là que la fonction native JavaScript parseInt() peut être appliqué. Remarque :dans la fonction, this fait référence au document que l'opération de réduction de carte est en train de traiter :

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

Ensuite, définissez la fonction reduce correspondante avec deux arguments keySubject et valuesMarks . valuesMarks est un tableau dont les éléments sont l'entier marks valeurs émises par la fonction map et regroupées par keySubject .La fonction réduit les valuesMarks tableau à la somme de ses éléments.

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

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

Avec votre collection, ce qui précède placera votre résultat d'agrégation MapReduce dans une nouvelle collection db.example_results . Ainsi, db.example_results.find() affichera :

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