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

    作者:田小计划
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
    如果觉得不错,请点击推荐关注
  • 相关阅读:
    url 编码与解码
    调硬件开门
    JsBridge 开灯关灯
    滚动条样式
    uni-app 组件传值及插槽
    Eapp 调接口及跳转
    uni-app 的基础格式
    Eapp 几个弹框
    flexible 移动端适配
    mongodb恢复备份
  • 原文地址:https://www.cnblogs.com/wilber2013/p/4141262.html
Copyright © 2011-2022 走看看