mongodb聚合查询未使用$ sum返回正确的总和

时间:2015-04-10 12:24:04

标签: mongodb mongodb-query aggregation-framework

我为学生收集了以下格式的文件: -

{
 _id:"53fe74a866455060e003c2db",
 name:"sam",
 subject:"maths",
 marks:"77"
}
{
 _id:"53fe79cbef038fee879263d2",
 name:"ryan", 
 subject:"bio",
 marks:"82"
}
{
 _id:"53fe74a866456060e003c2de",
 name:"tony",
 subject:"maths",
 marks:"86"
}

我想得到所有学生的总分数= subject =“maths”。所以我应该得到163作为总和。

db.students.aggregate([{ $match : { subject : "maths" } },
{ "$group" : { _id : "$subject", totalMarks : { $sum : "$marks" } } }])

现在我应该得到以下结果 -

{"result":[{"_id":"53fe74a866455060e003c2db", "totalMarks":163}], "ok":1}

但我得到了 -

{"result":[{"_id":"53fe74a866455060e003c2db", "totalMarks":0}], "ok":1}

有人可以指出我在这里做错了吗?

2 个答案:

答案 0 :(得分:4)

您当前的架构将marks字段数据类型作为字符串,您需要一个整数数据类型供您的聚合框架计算总和。另一方面,您可以使用MapReduce来计算总和,因为它允许在其地图函数中的对象属性上使用parseInt()等本机JavaScript方法。总的来说,你有两个选择。


选项1:更新架构(更改数据类型)

第一个是更改架构或在文档中添加具有实际数值而不是字符串表示的另一个字段。如果您的收藏文档尺寸相对较小,您可以结合使用mongodb光标 find() forEach() update() 更改标记架构的方法:

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

对于相对较大的集合大小,您的数据库性能会很慢,建议您使用mongo bulk updates

MongoDB版本> = 2.6和< 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版本3.2及更新版本:

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

选项2:运行MapReduce

第二种方法是使用MapReduce重写您的查询,您可以使用JavaScript函数parseInt()

MapReduce操作中,定义处理每个输入文档的地图功能。此函数将转换后的marks字符串值映射到每个文档的subject,并发出subject和转换后的marks对。这是可以应用JavaScript本机函数parseInt()的地方。注意:在函数中,this指的是map-reduce操作正在处理的文档:

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

接下来,使用两个参数keySubjectvaluesMarks定义相应的reduce函数。 valuesMarks是一个数组,其元素是map函数发出的整数marks值,并按keySubject分组。 该函数将valuesMarks数组缩减为其元素之和。

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

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

通过您的收藏,上面的内容会将您的MapReduce聚合结果放入新的集合db.example_results中。因此,db.example_results.find()将输出:

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

答案 1 :(得分:0)

你的总和被返回0的可能原因是:

  1. 您要汇总的字段不是整数,而是字符串。

    确保该字段包含数值。

  2. 您使用的是$ sum的错误语法。

    db.c1.aggregate([{ $group: { _id: "$item", price: { $sum: "$price" }, count: { $sum: 1 } } }])

    确保使用“$ price”而不是“price”。

  3. 发生此错误的最愚蠢的错误之一是:

    在指定字段名称时使用引号内的空格或制表符。

    示例 - “$ price”无效!!! “$ price”可行。