mongodb上的多次总数/计数(总和性别和总结果)

时间:2015-07-17 20:36:25

标签: mongodb aggregation-framework

我有这个文件:

{gender:"male", ...},
{gender:"male", ...},
{gender:"female", ...},
{gender:"female", ...},
{gender:"female", ...},

所以,我需要像

一样进行反击
{
total:5,
male:2,
female:3
}

我的实际查询(没有工作):

db.collection.aggregate([
{
    $match:{...}
},
{
    $group:{
        _id:"$_id",
        gender:{
            $push:"$gender"
        },
        total:{
            $sum:1
        }
    }
},
{
    $unwind:"$gender"
},
{
    $group:{
        _id:"$gender",
        name:{
            $addToSet:"$all"
        },
        "count":{
            $sum:1
        }
    }
}
])

我如何能够追溯性别和总数? 感谢

2 个答案:

答案 0 :(得分:5)

这样的事情可以解决问题:

db.collection.aggregate([
  {$project: {
    male: {$cond: [{$eq: ["$gender", "male"]}, 1, 0]},
    female: {$cond: [{$eq: ["$gender", "female"]}, 1, 0]},
  }},
  {$group: { _id: null, male: {$sum: "$male"},
                        female: {$sum: "$female"},
                        total: {$sum: 1},
  }},
])

根据你的例子制作:

{ "_id" : null, "male" : 2, "female" : 3, "total" : 5 }

关键的想法是使用conditional expression将性别映射到0或1.之后,您只需要对每个字段进行简单求和。

答案 1 :(得分:0)

 

可以通过多种方式获得结果,但它确实有助于理解如何获得结果以及聚合管道正在做什么。

因此,这里的一般情况是测试“性别”的值,然后决定是否累积该性别的总数。因此,可以使用$eq运算符中的$cond测试通过逻辑分隔字段。但最有效的方法是直接处理$group

var start = Date.now();
db.people.aggregate([
    { "$group": {
        "_id": null,
        "male": { 
            "$sum": { 
                "$cond": [
                    { "$eq": ["male","$gender"] },
                   1,
                   0
                ]
            }
        },
        "female": { 
            "$sum": { 
                "$cond": [
                    { "$eq": ["female","$gender"] },
                   1,
                   0
                ]
            }
        },
        "total": { "$sum": 1 }
    }}
])
var end = Date.now();
end - start;

现在在我的小笔记本电脑上有一个合理均匀的随机“性别”样本,管道需要290ms一致地运行,因为每个文档都要评估哪些字段是总和并同时求和。

另一方面,如果您按照其他地方的建议写入$project阶段:

var start = Date.now();
db.people.aggregate([
    { "$project": {
        "male": { 
            "$cond": [
                { "$eq": ["male","$gender"] },
               1,
               0
            ]
        },
        "female": { 
            "$cond": [
                { "$eq": ["female","$gender"] },
               1,
               0
            ]
        },
    }},
    { "$group": {
        "_id": null,
        "male": { "$sum": "$male" },
        "female": { "$sum": "$female" },
        "total": { "$sum": 1 }
    }}
])
var end = Date.now();
end - start;

然后平均结果是460ms来运行管道,并且接近“双倍”的时间。那么这里发生了什么?

基本上$project需要处理集合中的每个文档“之前”它们被发送到$group阶段,这正是它所做的。你有一个管道改变了每个(测试中的100000)文档的结构,然后再对它做任何其他事情。

这就是能够在逻辑上看起来有用的地方,然后说“暂停一下,为什么我在那里做那个这里,然后认识到所有逻辑都压缩成一个阶段。

这就是设计和优化的全部内容。所以,如果你要学习,那么学习正确的方法会有所帮助。

样本生成:

var bulk = db.people.initializeOrderedBulkOp(),
    count = 0,
    gender = ["male","female"];

for ( var x=1; x<=100000; x++ ) {
    bulk.insert({
        "gender": gender[Math.floor(Math.random()*2)]
    });
    count++;

    if ( count % 1000 == 0 ) {
        bulk.execute();
        bulk = db.people.initializeOrderedBulkOp();
    }
}

两条管道的结果:

{ "_id" : null, "male" : 50086, "female" : 49914, "total" : 100000 }

<强>计时

主体中提供的“客户端”时间当然包括客户端解释和执行的开销时间,以及传输,但这是在本地服务器上。

我重新运行了一个新的MongoDB 3.0.3 Ubuntu 15.04虚拟机(分配了4个内核的2GB分配)上的日志分析了一个非常老的英特尔酷睿i7笔记本电脑主机8GB和Windows 7 64位我从不打扰覆盖。

服务器上的实际时间仅来自日志平均每次执行1000次(预热):

  

单个$ group优化:平均值: 185毫秒分钟: 98毫秒最大值: 205毫秒

     

seprate $ project: avg: 330ms min: 316ms max: 410ms

所以,实际上一个“小”稍微接近“更糟糕”,几乎时间翻了一倍。但这正是我对结果的期望。因此,这里总体“成本”的近50%是将数据加载和处理到存储器中的管道中。因此,差异来自于能够在加载和处理的同时减少结果。