zoukankan      html  css  js  c++  java
  • MongoDB中的聚合操作

    根据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命令可以支持的选项:

    1 { count: <collection>, query: <query>, limit: <limit>, skip: <skip>, hint: <hint> }
    • count:要执行count的collection
    • query(optional):过滤条件
    • limit(optional):查询匹配文档数量的上限
    • skip(optional):跳过匹配文档的数量
    • hint(optional):使用那个索引

    例子:查看男学生的数量

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

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

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

    distinct

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

    1 { distinct: "<collection>", key: "<field>", query: <query> }
    • distinct:要执行distinct的collection
    • key:要执行distinct的键
    • query(optional):过滤条件

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

     1 > db.runCommand({"distinct":"school.students","key":"age"})
     2 {
     3         "values" : [
     4                 20,
     5                 21,
     6                 22,
     7                 23,
     8                 24
     9         ],
    10         "stats" : {
    11                 "n" : 10,
    12                 "nscanned" : 10,
    13                 "nscannedObjects" : 0,
    14                 "timems" : 0,
    15                 "cursor" : "BtreeCursor age_1"
    16         },
    17         "ok" : 1
    18 }

    group

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

     1 {
     2   group:
     3    {
     4      ns: <namespace>,
     5      key: <key>,
     6      $reduce: <reduce function>,
     7      initial: 
     8      $keyf: <key function>,
     9      cond: <query>,
    10      finalize: <finalize function>
    11    }
    12 }
    • ns:要执行group的collection
    • key:要执行group的键,可以是多个键;和keyf两者必须有一个
    • $reduce:在group操作中要执行的聚合function,该function包括两个参数,当前文档和聚合结果文档
    • initial:reduce中使用变量的初始化
    • $keyf(optional):可以接受一个function,用来动态的确定分组文档的字段
    • cond(optional):过滤条件
    • finalize(optional):在reduce执行完成,结果集返回之前对结果集最终执行的函数

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

     1 > db.runCommand({
     2 ...     "group":{
     3 ...         "ns":"school.students",
     4 ...         "key":{"age":true, "gender":true},
     5 ...         "initial":{"count":0},
     6 ...         "$reduce": function(cur, result){ result.count++;},
     7 ...         "cond":{"age":{"$lte":22}}
     8 ...     }
     9 ... })
    10 {
    11         "retval" : [
    12                 {
    13                         "age" : 20,
    14                         "gender" : "Female",
    15                         "count" : 2
    16                 },
    17                 {
    18                         "age" : 20,
    19                         "gender" : "Male",
    20                         "count" : 1
    21                 },
    22                 {
    23                         "age" : 21,
    24                         "gender" : "Male",
    25                         "count" : 2
    26                 },
    27                 {
    28                         "age" : 22,
    29                         "gender" : "Female",
    30                         "count" : 1
    31                 }
    32         ],
    33         "count" : 6,
    34         "keys" : 4,
    35         "ok" : 1
    36 }
    37 >

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

     1 > db.runCommand({
     2 ...     "group":{
     3 ...         "ns":"school.students",
     4 ...         "key":{"age":true, "gender":true},
     5 ...         "initial":{"count":0},
     6 ...         "$reduce": function(cur, result){
     7 ...                             result.count++;
     8 ...                         },
     9 ...         "cond":{"age":{"$lte":22}},
    10 ...         "finalize": function(result){
    11 ...                             result.percentage = result.count/10;
    12 ...                             delete result.count;
    13 ...                         }
    14 ...     }
    15 ... })
    16 {
    17         "retval" : [
    18                 {
    19                         "age" : 20,
    20                         "gender" : "Female",
    21                         "percentage" : 0.2
    22                 },
    23                 {
    24                         "age" : 20,
    25                         "gender" : "Male",
    26                         "percentage" : 0.1
    27                 },
    28                 {
    29                         "age" : 21,
    30                         "gender" : "Male",
    31                         "percentage" : 0.2
    32                 },
    33                 {
    34                         "age" : 22,
    35                         "gender" : "Female",
    36                         "percentage" : 0.1
    37                 }
    38         ],
    39         "count" : 6,
    40         "keys" : 4,
    41         "ok" : 1
    42 }
    43 >

     

    mapReduce

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

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

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

     1 {
     2     mapReduce: <collection>,
     3     map: <function>,
     4     reduce: <function>,
     5     out: <output>,
     6     query(optional): <document>,
     7     sort(optional): <document>,
     8     limit(optional): <number>,
     9     finalize(optional): <function>,
    10     scope(optional): <document>,
    11     jsMode(optional): <boolean>,
    12     verbose(optional): <boolean>
    13 }
    • 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文档

    例子:

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

     1 > db.runCommand({
     2 ...     "mapReduce": "school.students",
     3 ...     "map": function(){
     4 ...                     emit({gender: this.gender}, this.age);
     5 ...                 },
     6 ...     "reduce": function(key, values){
     7 ...                         var max = 0;
     8 ...                         for(var i = 0; i < values.length; i++)
     9 ...                             max = max>values[i]?max:values[i];
    10 ...                         return max;
    11 ...                    },
    12 ...     "out": {inline: 1},
    13 ...
    14 ... })
    15 {
    16         "results" : [
    17                 {
    18                         "_id" : {
    19                                 "gender" : "Female"
    20                         },
    21                         "value" : 24
    22                 },
    23                 {
    24                         "_id" : {
    25                                 "gender" : "Male"
    26                         },
    27                         "value" : 24
    28                 }
    29         ],
    30         "timeMillis" : 2,
    31         "counts" : {
    32                 "input" : 10,
    33                 "emit" : 10,
    34                 "reduce" : 2,
    35                 "output" : 2
    36         },
    37         "ok" : 1
    38 }
    39 >

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

     1 > db.runCommand({
     2 ...     "mapReduce": "school.students",
     3 ...     "map": function(){
     4 ...                     emit({gender: this.gender}, this.age);
     5 ...                 },
     6 ...     "reduce": function(key, values){
     7 ...                         var result = {"total": 0, "count": 0};
     8 ...                         for(var i = 0; i < values.length; i++)
     9 ...                             result.total += values[i];
    10 ...                         result.count = values.length;
    11 ...                         return result;
    12 ...                    },
    13 ...     "out": {inline: 1},
    14 ...     "finalize": function(key, reducedValues){
    15 ...                         return reducedValues.total/reducedValues.count;
    16 ...                    }
    17 ... })
    18 {
    19         "results" : [
    20                 {
    21                         "_id" : {
    22                                 "gender" : "Female"
    23                         },
    24                         "value" : 22
    25                 },
    26                 {
    27                         "_id" : {
    28                                 "gender" : "Male"
    29                         },
    30                         "value" : 21.8
    31                 }
    32         ],
    33         "timeMillis" : 55,
    34         "counts" : {
    35                 "input" : 10,
    36                 "emit" : 10,
    37                 "reduce" : 2,
    38                 "output" : 2
    39         },
    40         "ok" : 1
    41 }
    42 >

    小技巧:关于自定义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中。

     1 > db.runCommand({
     2 ...     "mapReduce": "school.students",
     3 ...     "map": function(){
     4 ...                     emit({gender: this.gender}, this.age);
     5 ...                 },
     6 ...     "reduce": function(key, values){
     7 ...                         avg = Avg(key, values);
     8 ...                         return avg;
     9 ...                    },
    10 ...     "out": {"merge": "average_age"}
    11 ... })
    12 {
    13         "result" : "average_age",
    14         "timeMillis" : 30,
    15         "counts" : {
    16                 "input" : 10,
    17                 "emit" : 10,
    18                 "reduce" : 2,
    19                 "output" : 2
    20         },
    21         "ok" : 1
    22 }
    23 >

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

     1 > show collections
     2 average_age
     3 school.students
     4 system.indexes
     5 system.js
     6 >
     7 > db.average_age.find()
     8 { "_id" : { "gender" : "Female" }, "value" : 22 }
     9 { "_id" : { "gender" : "Male" }, "value" : 21.8 }
    10 >
    11 > db.system.js.find()
    12 { "_id" : "Sum", "value" : function (key,values)
    13 {
    14     var total = 0;
    15     for(var i = 0; i < values.length; i++)
    16         total += values[i];
    17     return total;
    18 } }
    19 { "_id" : "Avg", "value" : function (key,values)
    20 {
    21     var total = Sum(key,values);
    22     var mean = total/values.length;
    23     return mean;
    24 } }
    25 >

    总结

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

    Ps: 文章中使用的例子可以通过以下链接查看

    http://files.cnblogs.com/wilber2013/aggregation.js

    作者:田小计划
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
    如果觉得不错,请点击推荐关注
  • 相关阅读:
    Nim or not Nim? hdu3032 SG值打表找规律
    Maximum 贪心
    The Super Powers
    LCM Cardinality 暴力
    Longge's problem poj2480 欧拉函数,gcd
    GCD hdu2588
    Perfect Pth Powers poj1730
    6656 Watching the Kangaroo
    yield 小用
    wpf DropDownButton 源码
  • 原文地址:https://www.cnblogs.com/wilber2013/p/4141262.html
Copyright © 2011-2022 走看看