美文网首页
7. 聚合( 重点 )

7. 聚合( 重点 )

作者: 简人CC | 来源:发表于2017-09-26 21:01 被阅读0次

    MongoDB的产生背景是在大数据环境,所谓的大数据实际上也就是进行的信息收集汇总。那么就必须存在有信息的统计操作,而这样的统计操作就称为聚合(直白: 分组统计就是一种聚合操作 ).

    7.1 取得集合的数据量

    对于集合的数据量而言, 在MongoDB里面直接使用count()函数就可以完成了。

    范例: 统计students表中的数据量

    db.students.count()

    范例: 模糊查询

    db.students.count({"name": /张/i})

    在进行信息查询的时候, 不设置条件永远要比设置条件的查询快很多, 也就是说在之前的代码编写里面不管是查询全部还是模糊查询, 实际上最终都是用的模糊查询一种(没有设置关键字)。

    7.2 消除重复数据

    在学习SQL的时候对于重复的数据可以使用"DISTINCT", 那么这一操作在MongoDB之中依然支持.

    范例: 查询所有name的信息

    本次的操作没有直接的函数支持,只能够利用runCommand()函数.

    db.runCommand({"distinct": "students", "key": "name})

    此时实现了对于name数据的重复值的筛选.

    7.3 group操作

    使用"group"操作可以实现数据的分组操作, 在MongoDB里面会将集合一句指定的key的不同进行分组操作,并且每一个组都会产生一个处理的文档信息.

    查询所有年龄大于等于19岁的学生信息,并且按照年龄分组

    db.runCommand({"group": {
    //指定要进行分组的集合
      "ns": "students",
    //指定文档分组的依据,这里是username键的值相等的被划分到一组, true为返回键username的值
      "key": {"age": true},
    //每一组reduce函数调用的初始个数,每一组的所有成员都会使用这个累加器。
      "initial": {"count": 0},
    //这个age的值大于等于19
      "condition": {"age": {"$gte": 19}},
    //每个文档都对应的调用一次.系统会传递两个参数: 当前文档和累加器文档
      "$reduce": function(doc, prev) {
        prev.count++
      }
    }})
    
    db.students.group({
     key: {sex: 1},
     cond: {age: {"$age": 19}},
     reduce: function(cur, result) { result.count ++ },
     initial: {count: 0}
    })
    

    以上的操作代码里面实现的就属于一种MapReducer,但是这样只要根据传统的数据库的设计思路,实现了一个所谓的分组操作,但是这个分组操作的最终结果是有限的

    7.3 MapReduce

    Mapreduce是整个大数据的精髓所在(实际中别用 ),所谓的MapReducer就是分为两部处理数据:
    Map: 将数据分别取出.
    Reducer: 负责数据的最后处理.

    可以要想在MongoDB里面实现MapReducer处理,那么复杂度是相当高的。

    范例: 建立一组雇员数据

    db.emps.insert({name: '张三',age: 30, sex: '男', job: 'CLERK', salary: 1000})
    db.emps.insert({name: '李四',age: 28, sex: '女', job: 'Manger', salary: 9000})
    db.emps.insert({name: '王五',age: 26, sex: '男', job: 'CLERK', salary: 1000})
    db.emps.insert({name: '赵六',age: 32, sex: '女', job: 'PRESIDENT', salary: 5000})
    db.emps.insert({name: '孙七',age: 31, sex: '女', job: 'CLERK', salary: 1000})
    db.emps.insert({name: '王八',age: 35, sex: '男', job: 'Manger', salary: 1000})
    

    使用MapReducer操作最终会将处理结果保存在一个单独的集合里面, 而最终的处理效果如下.

    范例: 按照职位分组, 取得每个职位的人名

    编写分组的定义

    var jobMapFun = function () {
     emit(this.job, this.name) //按照job分组, 取出name
    }
    
    第一组: {key: "CLERK", values: [姓名,姓名,...]}
    

    第二步: 编写reducer操作

    var jobReducerFun = function() {
     return {"job": key, "names": values}
    }
    

    第三步: 针对于MapReducer处理完成的数据实际上也可以执行一个最后处理.

    var jobFinalizeFun = function(key, values) {
     if(key == "PRESIDENT") {
         return {obj:  key, names: values, info: '公司的老大'}
      }
    
    return {obj:  key, names: values}
    }
    

    进行操作的整合

    db.runCommand({
      "mapreduce": "emps",
      "map": jobMapFun,
      "reduce": jobReducerFun,
      "out": "t_job_map",
      "finalize": jobFinalizeFun
    })
    

    现在执行之后, 所有的处理结果都保存在了"t_job_emp"集合里面.

    db.t_job_emp.find().pretty()

    范例: 统计出各性别的人数、平均工资、最低工资、雇员工资

    var sexMapFun = function() {
     //定义好了分组的条件,以及每个集合要取出的内容
    emit(this.sex, {"count": 1, "csal": this.salary, "cmax":this.salary, "cmin": this.salary, "cname': this.name}
    }
    
    
    var sexRedecerFun = function(key, values) {
      var total = 0 //统计
      var sum = 0 //计算总工资
      var max = values[0].NaNax//假设第一个数据是最高工资
      var min = values[0]NaNin//假设第一个数据是最低工资
     var names = [] //定义数组内容
     for(var x in values) {
           total +=values[x].count//人数增加
           sum += values[x].csal//累加工资
    
         if(max < values[x]NaNax) { //不是最高工资
           max = values[x]NaNax
         }
    
         if(min > values[x]NaNin) { //不是最低工资
           min = values[x]NaNin
         }
     }
     var avg = (sum / total ).toFixed(2)
    }
    
    db.runCommand({
      "mapreduce": "emps",
      "map": sexMapFun,
      "reduce": sexReduceFun,
      "out": "t_sex_emp"
    })
    

    虽然大数据的时代提供有强悍的MapReduce支持,但是从现实的开发来讲,真的不可能使用起来。

    7.5 聚合框架(核心)

    MapReduce功能强大, 但是它的复杂度和功能一样强大, 那么很多时候我们需要MapReduce的功能,可以又不想把代码写得太复杂, 所以从Mongo 2.X版本之后之后开始引入了聚合框架并且提供了聚合函数: aggregate()

    7.5.1 $group

    group主要进行分组的数据操作。
    范例: 实现聚和查询的功能 - 求出每个职位的雇员人数

    db.emps.aggregate([{"$group": {"_id": "$job",job_count: {"$sum": 1}}}])

    这样的操作更加复合与传统的group by 子句的操作.

    范例: 求出每个职位的总工资

    db.emps.aggregate([{"$group": {"_id": "$sex", salary_count: {"$sum": "$salary"}}}])

    在整个聚合框架里面如果要引用每行的数据使用: "$字段名称"

    范例: 计算出每个职位的平均工资

    db.emps.aggregate([{
      "$group": {
        "_id": "$job",
        "job_sal": {"$sum": "$salary"},
        "job_avg": {"$avg": "$salary"}
      }
    }])
    

    范例: 求出最高与最低工资

    db.emps.aggregate([{
      "$group": {
        "_id": "$job",
        "max_sal": {"$max": "$salary"},
        "min_sal": {"$min": "$salary"}
      }
    }])
    

    以上的几个与SQL类似的操作计算就成功的实现了。

    范例: 计算出每个职位的工资数据(数组显示)

    db.emps.aggregate([{
      "$group": {
        "_id": "job",
        "sal_data": {"$push": "$salary"}
      }
    }])
    

    范例: 求出每个职位的人员

    db.emps.aggregate([{
      "$group": {
        "_id": "job",
        "sal_data": {"$push": "$name"}
      }
    }])
    

    使用"$push"的确可以将数据变为数组进行保存,但是有一股问题出现了, 重复的内容也会进行保存,那么在MongoDB里面保存有取消重复的设置.

    范例: 取消重复的数据

    db.emps.aggregate([{
      "$group": {
        "_id": "job",
        "sal_data": {"$addToSet": "$name"}
      }
    }])
    

    默认情况行下是将所有数据的数据都保存进去了, 但是现在只希望可以保留第一个或者最后一个。

    保留第一个内容

    db.emps.aggregate([{
      "$group": {
        "_id": "job",
        "sal_data": {"$first": "$name"}
      }
    }])
    

    保留最后一个内容

    db.emps.aggregate([{
      "$group": {
        "_id": "job",
        "sal_data": {"$last": "$name"}
      }
    

    虽然可以方便的实现分组处理, 但是有一点需要注意,所有的分组数据是无序的, 并且都是在内存之中完成的, 所以不可能支持大数据量.

    7.5.2 $project

    可以利用"$project" 来控制数据列的显示规则,那么可以执行的规则如下:
    |- 普通列({成员: 1 | true}}): 便是要显示的内容;
    |- "_id"列({"_id": 0 | false}): 表示"_id"列是否显示;
    |- 条件过滤器({成员: 表达式}):满足表达式之后的数据可以进行显示.

    范例: 只显示name、job列, 不显示"_id"列

    db.emps.aggregate([{"$project": {
      "_id": 0,
      "name": 1
    }}])
    

    此时只有设置进去的列才可以被显示出来, 而其他的列不能够被显示出来。实际上这就属于数据库的投影机制。
    实际上在进行数据投影的过程里面也支持四则运算:加法("$add" )、减法("$subtract")、乘法("$multipty")、除法("$divide" )、求模($mod).

    范例: 观察四则运算

    db.emps.aggregate([{
      "$group": {
        "_id": "$job",
        "max_sal": {"$max": "$salary"},
        "min_sal": {"$min": "$salary"}
      }
    }])
    
    db.emps.aggregate([{
      "$group": {
        "_id": "job",
        "sal_data": {"$push": "$name"}
      }
    }])
    
    db.emps.aggregate([{
      "$group": {
        "_id": "job",
        "sal_data": {"$addToSet": "$name"}
      }
    }])
    
    db.emps.aggregate([{
      "$group": {
        "_id": "job",
        "sal_data": {"$last": "$name"}
      }
    }])
    
    db.emps.aggregate([{
      "$project": {
        "_id": 0,
        "name": 1,
        "job": 1,
        "salary": 1
      }
    }])
    

    除了四则运算之外也支持如下的各种运算符:
    关系运算:大小比较(“$cmp”)、等于(“$eq”)、大于(“$gt”)、大于等于(“$gte”)、小于(“$lt”)、小于等于(“$lte”)、不等于(“$ne”)、判断NULL(“$ifNull”),这些返回的结果都是布尔型数据;

    ·逻辑运算:与(“$and”)、或(“$or”)、非(“$not”);

    ·字符串操作:连接(“$concat”)、截取(“$substr”)、转小写(“$toLower”)、转大写(“toUpper”)、不区分大小写比较(“$strcasecmp”)。

    范例: 找出所有工资大于等于2000的雇员姓名、年龄、工资

    db.emps.aggregate([{
      "$project": {
        "_id":0,
        "name": 1,
        "salary": 1,
        "age": 1,
        "salary": {"$gte": ["$salary", 2000]}
      }
    }])
    

    范例:查询职位是manger的信息

    MongoDB中的数据是区分大小写的;

    db.emps.aggregate([{
      "$project": {
        "_id": 0,
        "name": 1,
        "age": 1,
        "job": {
          "$eq": ["$job", {"$toUpper": "Manger"}]
        }
      }
    }])
    
    
    db.emps.aggregate([{
      "$project": {
        "_id": 0,
        "name": 1,
        "job": {"$strcasecmp": ["$job", "Manger"]}
      }
    }])
    

    范例:使用字符串截取

    db.emps.aggregate([{
      "$project": {
        "_id": 0,
        "name": 1,
        "job": {"$substr": ["$job", 0, 3]}
      }
    }])
    

    利用"$project" 实现的投影操作功能相当强大,所有可以出现的操作几乎都能够使用。

    7.5.3 $match

    "$match" 是一个滤波操作,就是进行WHERE的过滤.

    范例:查询工资在2000 ~5000的雇员

    db.emps.aggregate([
      {
        "$match": {
          "salary": {"$gte": 2000, "$lte": 5000}
        }
      }
    ])
    

    这个时候实现的代码严格来讲只是相当于“SELECT * FROM 表 WHERE 条件”,属于所有的内容都进行了查询。

    范例:控制投影操作

    db.emps.aggregate([
      {
        "$match": {
          "salary": {"$gte": 2000, "$lte": 5000}
        }
      },
      {
        "$project": {
          "_id": 0,
          name: 1,
          age: 1
        }
      }
    ])
    
    

    此时相当于实现了“SELECT字段 FROM ... WHERE”语句结构。

    范例: 继续分组

    db.emps.aggregate([
      {
        "$match": {
          "salary": {"$gte": 2000, "$lte": 5000}
        }
      },
      {
        "$project": {
          "_id": 0,
          name: 1,
          age: 1
        }
      },
      {
        "$group": {
          "_id": "$job",
          "count": {"$sum": 1},
          "avg": {
            "$avg": "$salary"
          }
        }
      }
    ])
    

    通过一些列的演示可以总结一点
    "$project":相当于SELECT子句;
    "$match":相当于WHERE子句;
    "$group": 相当于GROUP BY子句。

    7.5.4 $sort

    使用"$sort" 可以实现排序,设置为1表示升序,设置为-1表示降序.

    范例: 实现排序

    db.emps.aggregate([{ "$sort": {"age": -1, "salary": 1}}])

    范例: 将所有操作一起使用

    db.emps.aggregate([
      {
        "$match": {
          "salary": {"$gte": 2000, "$lte": 5000}
        }
      },
      {
        "$project": {
          "_id": 0,
          name: 1,
          age: 1
        }
      },
      {
        "$group": {
          "_id": "$job",
          "count": {"$sum": 1},
          "avg": {
            "$avg": "$salary"
          }
        }
      },
      {
        "$sort": {"count": -1}
      }
    ])
    

    此时实现了降序排序,使用的是生成定义的别名。

    7.5.5 分页处理: $limit、$skip

    “$limit”:负责数据的取出个数;

    “$skip”:数据的跨过个数。

    范例: 使用"$limit"设置取出的个数

    db.emps.aggregate([
      {
        "$project": {"_id": 0, "name": 1, "salary": 1, "job": 1},
      },
      {
        "$limit": 2
      }
    ])
    

    跨过3行数据

    db.emps.aggregate([
      {
        "$project": {"_id": 0, "name": 1, "salary": 1, "job": 1},
      },
      {
        "$skip": 3
      }
      {
        "$limit": 2
      }
    ])
    

    综合运用

    db.emps.aggregate([
      {
        "$match": {
          "salary": {"$gte": 2000, "$lte": 5000}
        }
      },
      {
        "$project": {
          "_id": 0,
          name: 1,
          age: 1
        }
      },
      {
        "$group": {
          "_id": "$job",
          "count": {"$sum": 1},
          "avg": {
            "$avg": "$salary"
          }
        }
      },
      {
        "$sort": {"count": -1}
      },
      {
        "$skip": 3
      }
      {
        "$limit": 2
      }
    ])
    
    

    范例: 7.5.6 $unwind

    在查询数据的时候经常会返回数组信息,但是数组并不方便信息的浏览,所以提供有"$unwind"可以将数组变为独立的字符串内容

    添加一些信息

    db.nw.insert({"title": "技术部", "bus": ["研发", "生产", "培训"]})
    db.nw.insert({"title": "财务部", "bus": ["工资", "税收"]})
    

    范例: 将信息进行转化

    db.nw.aggregate([
      {
        "$project": {"_id": 0, "title": true, "bus": true},
      },
      {
        "$unwind": "$bus"
      }
    ])
    

    此时相当于将数组中的数据变为了单行数据.

    5.5.7 $geoNear

    使用"$geoNear"可以得到附近的坐标点

    范例:准备测试数据

    db.shop.drop()
    db.shop.insert({loc: [10, 10]})
    db.shop.insert({loc: [11, 10]})
    db.shop.insert({loc: [10, 11]})
    db.shop.insert({loc: [12, 15]})
    db.shop.insert({loc: [16, 17]})
    db.shop.insert({loc: [90, 90]})
    db.shop.insert({loc: [120, 130]})
    
    db.shop.ensureIndex({"loc": "2d"})
    

    范例: 设置查询

    db.shop.aggregate([
      {
        $geoNear: {
          near: { type: "Point", coordinates: [ 11 , 11 ] },
          distanceField: "dist.calculated",
          maxDistance: 2,
          query: { type: "public" },
          includeLocs: "dist.location",
          num: 5,
          spherical: true
        }
      }
    ])
    

    地理信息的检索必须存在有索引的支持。

    7.5.8 $out

    “$out”:利用此操作可以将查询结果输出到指定的集合里面。

    范例: 将投影的结果输出到集合里

    db.emps.aggregate([
      {"$project": {"_id": 0, "name": 1, "salary": 1, "job": 1}},
      {"$out": "emp_infos"}
    ])
    

    这类的操作就相当于实现了最早的数据表的复制操作.

    相关文章

      网友评论

          本文标题:7. 聚合( 重点 )

          本文链接:https://www.haomeiwen.com/subject/vypsextx.html