MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

Die Mongodb-Aggregatabfrage gibt bei Verwendung von $sum nicht die richtige Summe zurück

Ihr aktuelles Schema hat die marks Felddatentyp als Zeichenfolge und Sie benötigen einen ganzzahligen Datentyp für Ihr Aggregationsframework, um die Summe zu berechnen. Andererseits können Sie MapReduce verwenden um die Summe zu berechnen, da es die Verwendung von nativen JavaScript-Methoden wie parseInt() erlaubt auf Ihre Objekteigenschaften in seinen Kartenfunktionen. Insgesamt haben Sie also zwei Möglichkeiten.

Option 1:Schema aktualisieren (Datentyp ändern)

Die erste wäre, das Schema zu ändern oder ein weiteres Feld in Ihrem Dokument hinzuzufügen, das den tatsächlichen numerischen Wert und nicht die Zeichenfolgendarstellung enthält. Wenn Ihr Sammlungsdokument relativ klein ist, können Sie eine Kombination aus dem Mongodb-Cursor find() verwenden , forEach() und update() Methoden zum Ändern Ihres Markierungsschemas:

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

Bei relativ großen Sammlungsgrößen wird Ihre DB-Leistung langsam sein und es wird empfohlen, Mongo-Massenaktualisierungen dazu:

MongoDB-Versionen>=2.6 und <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 und neuer:

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:MapReduce ausführen

Der zweite Ansatz wäre, Ihre Abfrage mit MapReduce neu zu schreiben wo Sie die JavaScript-Funktion parseInt() verwenden können .

In Ihrem MapReduce Operation, definieren Sie die Zuordnungsfunktion, die jedes Eingabedokument verarbeitet. Diese Funktion bildet die konvertierten marks ab String-Wert zum subject für jedes Dokument und gibt den subject aus und umgewandelte marks Paar. Hier setzt die native JavaScript-Funktion parseInt() an Kann Angewandt werden. Hinweis:in der Funktion this bezieht sich auf das Dokument, das von der Map-Reduce-Operation verarbeitet wird:

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

Als nächstes definieren Sie die entsprechende Reduce-Funktion mit zwei Argumenten keySubject und valuesMarks . valuesMarks ist ein Array, dessen Elemente die ganzzahligen marks sind Werte, die von der Kartenfunktion ausgegeben und nach keySubject gruppiert werden .Die Funktion reduziert die valuesMarks Array zur Summe seiner Elemente.

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

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

Mit Ihrer Sammlung wird das Obige Ihr MapReduce-Aggregationsergebnis in eine neue Sammlung db.example_results einfügen . Also db.example_results.find() wird ausgegeben:

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