您的位置:首页 > 数据库 > Mongodb

MongoDB中的聚合操作

2014-12-03 22:24 330 查看
根据MongoDB的文档描述,在MongoDB的聚合操作中,有以下五个聚合命令。

其中,count、distinct和group会提供很基本的功能,至于其他的高级聚合功能(sum、average、max、min),就需要通过mapReduce来实现了。

在MongoDB2.2版本以后,引入了新的聚合框架(聚合管道,aggregation pipeline ,使用aggregate命令),是一种基于管道概念的数据聚合操作。

Name

Description

count

Counts the number of documents in a collection.

distinct

Displays the distinct values found for a specified key in a collection.

group

Groups documents in a collection by the specified key and performs simple aggregation.

mapReduce

Performs map-reduce aggregation for large data sets.

aggregate

Performs aggregation tasks such as group using the aggregation framework.

下面就开始对这些聚合操作进行介绍,所有的测试数据都是基于上一篇文章。

count

首先,我们看下MongoDB文档中,count命令可以支持的选项:

{ count: <collection>, query: <query>, limit: <limit>, skip: <skip>, hint: <hint> }


count:要执行count的collection

query(optional):过滤条件

limit(optional):查询匹配文档数量的上限

skip(optional):跳过匹配文档的数量

hint(optional):使用那个索引

例子:查看男学生的数量

> db.runCommand({"count":"school.students", "query":{"gender":"Male"}})
{ "n" : 5, "ok" : 1 }
>


在MongoDB中,对count操作有一层包装,所以也可以通过shell直接运行db."collectionName".count()。

但是为了保持风格一致,我还是倾向于使用db.runCommand()的方式。

distinct

接下来看看distinct命令,下面列出可以支持的选项:

{ distinct: "<collection>", key: "<field>", query: <query> }


distinct:要执行distinct的collection

key:要执行distinct的键

query(optional):过滤条件

例子:查看所有学生年龄的不同值

> db.runCommand({"distinct":"school.students","key":"age"})
{
"values" : [
20,
21,
22,
23,
24
],
"stats" : {
"n" : 10,
"nscanned" : 10,
"nscannedObjects" : 0,
"timems" : 0,
"cursor" : "BtreeCursor age_1"
},
"ok" : 1
}


group

group命令相比前两就稍微复杂了一些。

{
group:
{
ns: <namespace>,
key: <key>,
$reduce: <reduce function>,
initial:
$keyf: <key function>,
cond: <query>,
finalize: <finalize function>
}
}


ns:要执行group的collection

key:要执行group的键,可以是多个键;和keyf两者必须有一个

$reduce:在group操作中要执行的聚合function,该function包括两个参数,当前文档和聚合结果文档

initial:reduce中使用变量的初始化

$keyf(optional):可以接受一个function,用来动态的确定分组文档的字段

cond(optional):过滤条件

finalize(optional):在reduce执行完成,结果集返回之前对结果集最终执行的函数

例子:统计不同年龄、性别分组的学生数量

> db.runCommand({
...     "group":{
...         "ns":"school.students",
...         "key":{"age":true, "gender":true},
...         "initial":{"count":0},
...         "$reduce": function(cur, result){ result.count++;},
...         "cond":{"age":{"$lte":22}}
...     }
... })
{
"retval" : [
{
"age" : 20,
"gender" : "Female",
"count" : 2
},
{
"age" : 20,
"gender" : "Male",
"count" : 1
},
{
"age" : 21,
"gender" : "Male",
"count" : 2
},
{
"age" : 22,
"gender" : "Female",
"count" : 1
}
],
"count" : 6,
"keys" : 4,
"ok" : 1
}
>


通过finalize选项,可以在结果返回之前进行一些自定义设置。

> db.runCommand({
...     "group":{
...         "ns":"school.students",
...         "key":{"age":true, "gender":true},
...         "initial":{"count":0},
...         "$reduce": function(cur, result){
...                             result.count++;
...                         },
...         "cond":{"age":{"$lte":22}},
...         "finalize": function(result){
...                             result.percentage = result.count/10;
...                             delete result.count;
...                         }
...     }
... })
{
"retval" : [
{
"age" : 20,
"gender" : "Female",
"percentage" : 0.2
},
{
"age" : 20,
"gender" : "Male",
"percentage" : 0.1
},
{
"age" : 21,
"gender" : "Male",
"percentage" : 0.2
},
{
"age" : 22,
"gender" : "Female",
"percentage" : 0.1
}
],
"count" : 6,
"keys" : 4,
"ok" : 1
}
>


mapReduce

前面三个聚合操作提供了最基本的功能,如果要用到更加复杂的聚合操作,我们就需要自己通过mapReduce来实现了。

mapReduce更重要的用法是实现多个服务器上的聚合操作。

根据MongoDB文档,得到mapReduce的原型如下:

{
mapReduce: <collection>,
map: <function>,
reduce: <function>,
out: <output>,
query(optional): <document>,
sort(optional): <document>,
limit(optional): <number>,
finalize(optional): <function>,
scope(optional): <document>,
jsMode(optional): <boolean>,
verbose(optional): <boolean>
}


mapReduce:要执行map-reduce操作的collection

map:map function,生成键/值对,可以理解为映射函数

reduce:reduce function,对map的结果进行统计,可以理解为统计函数

out:统计结果存放集合 (不指定则使用临时集合,在客户端断开后自动删除)

query:过滤条件

sort:排序条件

limit:map函数可以接受的文档数量的最大值

finalize:在reduce执行完成后,结果集返回之前对结果集最终执行的函数

scope:向 map、reduce、finalize 导入外部变量

jsMode:设置是否把map和reduce的中间数据转换成BSON格式

verbose:设置是否显示详细的时间统计信息

注意:map、reduce和finalize的函数实现都有特定的要求,具体的要求请参考MongoDB文档

例子:

查询男生和女生的最大年龄

> db.runCommand({
...     "mapReduce": "school.students",
...     "map": function(){
...                     emit({gender: this.gender}, this.age);
...                 },
...     "reduce": function(key, values){
...                         var max = 0;
...                         for(var i = 0; i < values.length; i++)
...                             max = max>values[i]?max:values[i];
...                         return max;
...                    },
...     "out": {inline: 1},
...
... })
{
"results" : [
{
"_id" : {
"gender" : "Female"
},
"value" : 24
},
{
"_id" : {
"gender" : "Male"
},
"value" : 24
}
],
"timeMillis" : 2,
"counts" : {
"input" : 10,
"emit" : 10,
"reduce" : 2,
"output" : 2
},
"ok" : 1
}
>


分别得到男生和女生的平均年龄

> db.runCommand({
...     "mapReduce": "school.students",
...     "map": function(){
...                     emit({gender: this.gender}, this.age);
...                 },
...     "reduce": function(key, values){
...                         var result = {"total": 0, "count": 0};
...                         for(var i = 0; i < values.length; i++)
...                             result.total += values[i];
...                         result.count = values.length;
...                         return result;
...                    },
...     "out": {inline: 1},
...     "finalize": function(key, reducedValues){
...                         return reducedValues.total/reducedValues.count;
...                    }
... })
{
"results" : [
{
"_id" : {
"gender" : "Female"
},
"value" : 22
},
{
"_id" : {
"gender" : "Male"
},
"value" : 21.8
}
],
"timeMillis" : 55,
"counts" : {
"input" : 10,
"emit" : 10,
"reduce" : 2,
"output" : 2
},
"ok" : 1
}
>


小技巧:关于自定义js函数

在MongoDB中,可以通过db.system.js.save命令(其中system.js是一个存放js函数的collections)来创建并保存JavaScript函数,这样在就可以在MongoDB shell中重用这些函数。

比如,下面两个函数是网上网友实现的

SUM

db.system.js.save( { _id : "Sum" ,

value : function(key,values)

{

var total = 0;

for(var i = 0; i < values.length; i++)

total += values[i];

return total;

}});

AVERAGE

db.system.js.save( { _id : "Avg" ,

value : function(key,values)

{

var total = Sum(key,values);

var mean = total/values.length;

return mean;

}});

通过利用上面两个函数,我们的"分别得到男生和女生的平均年龄"例子就可以通过以下方式实现。

这个例子中,我们还特殊设置了"out"选项,把返回值存入了"average_age"这个collection中。

> db.runCommand({
...     "mapReduce": "school.students",
...     "map": function(){
...                     emit({gender: this.gender}, this.age);
...                 },
...     "reduce": function(key, values){
...                         avg = Avg(key, values);
...                         return avg;
...                    },
...     "out": {"merge": "average_age"}
... })
{
"result" : "average_age",
"timeMillis" : 30,
"counts" : {
"input" : 10,
"emit" : 10,
"reduce" : 2,
"output" : 2
},
"ok" : 1
}
>


通过以下命令,我们可以看到新增的collection,并且查看里面的内容。

> show collections
average_age
school.students
system.indexes
system.js
>
> db.average_age.find()
{ "_id" : { "gender" : "Female" }, "value" : 22 }
{ "_id" : { "gender" : "Male" }, "value" : 21.8 }
>
> db.system.js.find()
{ "_id" : "Sum", "value" : function (key,values)
{
var total = 0;
for(var i = 0; i < values.length; i++)
total += values[i];
return total;
} }
{ "_id" : "Avg", "value" : function (key,values)
{
var total = Sum(key,values);
var mean = total/values.length;
return mean;
} }
>


总结

通过这篇文章,介绍了MongoDB中count、distinct、group和mapReduce的基本使用。没有一次把所有的聚合操作都看完,聚合管道只能放在下一次了。

Ps: 文章中使用的例子可以通过以下链接查看
http://files.cnblogs.com/wilber2013/aggregation.js
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: