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
}