我有这个文档:
{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
}
}
}
])
如何检索性别和总数的计数器?由于
这样做就可以了:
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 }
关键思想是使用条件表达式将性别映射为0或1。之后,您所需要的就是对每个字段进行简单求和。
可以通过多种方式获得结果,但它确实有助于理解如何获得结果以及聚合管道正在做什么。
所以这里的一般情况是测试"gender"然后决定是否为那个性别累积总数。因此,可以通过逻辑在 $cond
操作符中使用 $eq
测试来分隔字段。但最有效的方法是直接处理 $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
运行管道,这接近于"double"时间。那么这是怎么回事呢?
基本上$project
需要处理集合中的每个文档"before"它们被送到$group
阶段,这正是它所做的。在对每个文档进行任何操作之前,这里有一个管道修改每个文档的结构(test中为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 }
计时
client"主体中提供的计时当然包括客户端解释和执行的实际开销时间,以及在本地服务器上传输的时间。
我重新运行并分析了一个新的MongoDB 3.0.3 Ubuntu 15.04 VM (2GB分配4核)上的日志,在一个相当旧的英特尔酷睿i7笔记本电脑主机上,8GB和Windows 7 64位,我从来没有费心去覆盖。
服务器上仅从日志中获取的平均每次执行1000次(预热)的实际计时:
单一美元集团最优: avg: 女士185分钟: 98 max女士: 205 ms
宽大的美元项目: avg: 女士330分钟: 316 max女士: 410 ms
所以实际上是一个"little"稍微接近于"更糟"几乎的时间翻了一倍,差距更大。但这正是我对结果的期望。因此,近50%的总"成本"这里是将数据加载并处理到内存中的管道中。所以不同之处在于能够在加载和处理的同时减少结果