MongoDB 聚合管道

MongoDB 聚合管道

    • 聚合管道概述
      • 需要注意的几个问题:
    • 聚合管道操作符
    • 聚合管道的使用
      • $project 指定文档显示的方式
      • 连接字段$concat用于连接字符串(参数必须是字符串类型数组)
      • 对字段进行分解 $substr
      • $match相当于find第一个参数,用于过滤文档
      • $limit $skip $sort的使用
      • $unwind
      • $lookup
      • g r o u p 分 组 , 一 般 结 合 group分组,一般结合 groupsum $max $avg $min(group最常用)
    • 单目的聚合命令
    • 基本练习

聚合管道概述

聚合管道由阶段组成
每个阶段由阶段操作符来对文档进行相应的处理
待处理的文档会流经各个阶段,最终完成计算

db.集合名.aggregate([{}])

//不改变以前数据,结果另存新的集合
{$out:"Result"}

需要注意的几个问题:

  • 在每个阶段对每条输入的文档不一定都有相应的输出
  • 聚合管道中,阶段是可以重复的( o u t 和 out和 outgeoNear除外)
  • 聚合管道可以在分片集合上使用
  • 聚合管道函数aggregate只能作用于一个集合

聚合管道操作符

聚合管道的基本功能:

  • 对文档进行过滤筛选符合条件的文档

  • 对文档进行变换,改变文档输出形式

    每个阶段的功能使用阶段操作符定义,在每个阶段操作符中可以使用表达式操作符计算平均值和拼接字符串等相关操作。
    aggregate是个集合,集合里面有一个或多个管道操作符

聚合管道的使用

  • m a t c h 和 match和 matchsort放于管道开始阶段,这样可以利用集合建立的索引来提高文档的处理效率

  • 提早过滤,在管道的初始阶段,可以使用 m a t c h , match, matchlimit以及$skip提早过滤,可以减少流经后续阶段的文档数量

db.juhe.insertMany([
   {
     name:{firstName:"alice",lastName:"wong"},
     balance:50
    },
    {
     name:{firstName:"bob",lastName:"yang"},
     balance:20
    }
  ])

db.juhe.aggregate([
    {
      $project:{
          _id:0,
          balance:1,
          clientName:"$name.firstName",
          newbalance:{$add:["$balance",10]}
      }
    }
])
$project:{_id:0,balance:1,newName:"$name.firstName",newBalance:{$add:["$balance",10]}}
//不要原来的id,要原来的balance,新名字为原来的第一个名字,新balance为原来+10

$project 指定文档显示的方式

db.col.aggregate([{$project:{_id:0}}])相当于
db.col.find({},{_id:0})

为name.firstName起个别名,还可以对某个字段的值进行运算后作为一个新字段
如果需要调用原字段的值,需要$field

middleName不存在,会用null来填充
多个字段组合成数组

db.juhe.aggregate([
    {
      $project:{
          _id:0,
          balance:1,
          nameArray:["$name.firstName","$name.middleName","$name.lastName"]
      }
    }
])

连接字段$concat用于连接字符串(参数必须是字符串类型数组)

db.juhe.aggregate([
    {
      $project:{
          nameFild:{$concat:["$name.firstName","-","$name.lastName"]}
      }
    }
])

对字段进行分解 $substr

{ $substr: [ <string expression>, <byte index>, <byte count> ] }

db.juhe.aggregate([
        {
          $project:{newName:{$substr:["$name.firstName",0,2]}}
          //结果  bo  a1
				//0:从第一个字符开始取
				//2:取两个字符
        }
 ])

总结:$project 相当于find第二个参数
1)规定哪些显示,哪些不显示;
2)为字段取别名;
3)为value值做操作(数字类型±*/,字符串类型:合并两列,取某一列的子串

$match相当于find第一个参数,用于过滤文档

m a t c h 限 制 了 显 示 的 行 数 , match限制了显示的行数, match,project限制了显示的列,以及每一列的内容

db.juhe.aggregate([
        {
          $match:{
             $or:[
               {balance:{$gt:40,$lt:80}},
               {"name.lastName":"yang"}
              ]
            }
         },
         {
           $project:{
                 _id:0
              }
         }
])

$limit $skip $sort的使用

for(i=1;i<11;i++){db.a11.insert({_id:i,name:"user"+i})}

db.a11.find().limit(3).skip(2)与db.a11.find().skip(2).limit(3)结果是相同的
跳过前两条,显示3条文档

db.a11.aggregate({$limit:3})
db.a11.aggregate({$skip:2})
注意:以下两种显示是不同的
db.a11.aggregate([{$skip:2},{$limit:3}])与db.a11.find().limit(3).skip(2)是相同的
注意管道先后顺序的作用,先跳过前2条,再显示3条
如果limit在skip之后优化器会做下面的转化
db.a11.aggregate([{$limit:5},{$skip:2}])


db.a11.aggregate([{$skip:2},{$limit:3},{$sort:{_id:-1}}])
$sort:{name:-1}

$unwind

{ $unwind: { 
path: <field path>, //需要展开的数组字段
includeArrayIndex: <string>, //可选,显示原数组元素的下标
preserveNullAndEmptyArrays: <boolean> //可选,不存在数组字段的文档是否显示
} }

将文档按照数组字段拆分成多条文档,每条文档包含数组中的一个元素

db.juhe.update(
    {"name.firstName":"alice"},
    {$set:{currency:["CNY","USD"]}}
)

db.juhe.update(
    {"name.firstName":"bob"},
    {$set:{currency:"GBP"}}
)

会把数组展开
db.juhe.aggregate([
    {
      $unwind:"$currency"
    }
])老版本的写法
展开数组时添加元素位置
db.juhe.aggregate([
    {
      $unwind:{
          path:"$currency",
          includeArrayIndex:"ccyIndex",

          preserveNullAndEmptyArrays:true
       }
    }
])

$lookup

实现了join操作

{ $lookup: { 
from: <collection to join>, 
localField: <field from the input documents>, 
foreignField: <field from the documents of the "from" collection>, 
as: <output array field> } }

from 同一数据库中的另一个查询集合
localField 管道文档中用来进行查询的字段
foreignField 查询集合中的字段
as 写入管道文档中的查询结果数组字段

db.forex.insertMany([
     {
       ccy:"USD",//美元
       rate:6.91,
       date:new Date("2019-12-21")
      },
     {
       ccy:"GBP",//英镑
       rate:8.72,
       date:new Date("2019-8-21")
      },
      {
       ccy:"CNY",//人民币
       rate:1.0,
       date:new Date("2019-12-21")
      }
])

将查询到外汇汇率写入银行账户文档
db.juhe.aggregate([
    {
      $lookup:{
        from:"forex",
        localField:"currency",
        foreignField:"ccy",
        as:"forexData"
       }
    }
])
db.juhe.aggregate([
    {
    $unwind:{
          path:"$currency"
          }
     },
     { $lookup:{
        from:"forex",
        localField:"currency",
        foreignField:"ccy",
        as:"forexData"
       }
      } 
])

g r o u p 分 组 , 一 般 结 合 group分组,一般结合 groupsum $max $avg $min(group最常用)

{KaTeX parse error: Expected 'EOF', got '}' at position 6: sum:1}̲ 统计该组有多少文档 {sum:"$filed"}对于组内的filed字段进行累加

db.transactions.insertMany([
   {
     symbol:"600519",
	 qty:100,
	 price:567.4,
	 currency:"CNY"
   },
   {
     symbol:"AMZN",
	 qty:1,
	 price:1377.5,
	 currency:"USD"
   },
   {
     symbol:"AAPL",
	 qty:2,
	 price:150.7,
	 currency:"USD"
   }
])

我想根据结算货币币种的不同,来看看我在世界上哪些地区有过交易,那我们就应该按照货币进行分组 就是currency

db.transactions.aggregate([
   {
     $group:{_id:"$currency"}
   }
])

很像关系数据库中的distinct
//_id为组,组内操作

db.transactions.aggregate([
   {
     $group:{
	 _id:"$currency",
	 totalQty:{$sum:"$qty"},
	 totalNotional:{$sum:{$multiply:["$price","$qty"]}},
	 avgPrice:{$avg:"$price"},
	 count:{$sum:1},
	 maxNotional:{$max:{$multiply:["$price","$qty"]}},
	 minNotional:{$min:{$multiply:["$price","$qty"]}}
	 }
   }
])

db.transactions.aggregate([
   {
     $group:{
	 _id:null,
	 totalQty:{$sum:"$qty"},
	 totalNotional:{$sum:{$multiply:["$price","$qty"]}},
	 avgPrice:{$avg:"$price"},
	 count:{$sum:1},
	 maxNotional:{$max:{$multiply:["$price","$qty"]}},
	 minNotional:{$min:{$multiply:["$price","$qty"]}}
	 }
   }
])
分组后,把文档中的某个字段的值放到一个数组里
db.transactions.aggregate([
   {
     $group:{
	 _id:"$currency",
	 symbols:{$push:"$symbol"}
	 }
   }
])

单目的聚合命令

db.inventory.distinct( “dept” )

单目的聚合操作count distinct
db.transactions.count()
db.transactions.distinct("symbol")

db.orders.count( { ord_dt: { $gt: new Date(‘01/01/2012’) } } )

基本练习

向自己姓名的集合中插入如下的文档:

db.xxx.insert({sno:1,name:"zhangsan1",sex:0,score:{chinese:69,math:79,english:80}})

db.xxx.insert({sno:2,name:"zhangsan1",sex:1,score:{chinese:60,math:78,english:80}})

db.xxx.insert({sno:3,name:"zhangsan2",sex:0,score:{chinese:60,math:79,english:90}})

db.xxx.insert({sno:4,name:"zhangsan2",sex:1,score:{chinese:50,math:79,english:80}})

db.xxx.insert({sno:5,name:"zhangsan2",sex:1,score:{chinese:60,math:60,english:80}})

db.xxx.insert({sno:6,name:"zhangsan3",sex:0,score:{chinese:60,math:79,english:80}})

db.xxx.insert({sno:7,name:"zhangsan4",sex:0,score:{chinese:60,math:75,english:80}})

db.xxx.insert({sno:8,name:"zhangsan4",sex:0,score:{chinese:60,math:79,english:86}})

db.xxx.insert({sno:9,name:"zhangsan5",sex:1,score:{chinese:66,math:79,english:80}})

db.xxx.insert({sno:10,name:"zhangsan5",sex:1,score:{chinese:60,math:78,english:80}})

db.xxx.insert({sno:11,name:"zhangsan5",sex:1,score:{chinese:60,math:79,english:80}})

1、根据姓名分组,并统计每组的人数

db.col.aggregate([{$group:{_id:"$name",total:{$sum :1}}}])

2、根据姓名分组,并统计人数,过滤人数大于1的学生

db.col.aggregate([{group:{_id:"$name",total:{$sum:1}}},{$match:{total:{$gt:1}}}])

3、统计每名学生在考试中的总分,按照学号分组

db.col.aggregate([{$project:{_id:0,sno:1,totalScore:{$add:["$score.chinese","$score.math","$chinese.english"]}}}])

4、统计每名男生在考试中的总分(sex=0为男生)

db.col.aggregate([{$match:{sex:0}},{$project:{_id:0,sno:1,name:1,sex:1,totalScore:{$add:["$score.chinese","$score.math","$chinese.english"]}}}])

5、统计每名男生在考试中的总分,总分降序

db.col.aggregate([{$match:{sex:0}},{$project:{_id:0,sno:1,name:1,sex:1,totalScore:{$add:["$score.chinese","$score.math","$chinese.english"]}}},{$sort:{totalScore:-1}}])

6、统计总分最高与总分最低的学生,显示时显示最高分的学号与最高分,最低分的学号与最低分

db.col.aggregate([{$project:{_id:"$sno",totalScore:{$add:["$score.chinese","$score.math","$chinese.english"]}}},{$sort:{totalScore:1}},{$group:{_id:"$sno",maxSn:{$last:"$_id"},maxST:{$last:"$totalScore"},minSn:{$first:"$_id"},minST:{$first:"$totalScore"}}}])

7、统计每名的学生的平均分数,显示学号,姓名与平均分数

db.col.aggregate([{$group:{_id:0,sno:1,name:1,avgScore:{$avg:["$score.chinese","$score.math","$chinese.english"]}}}])

8、统计所有学生总分的平均分数

db.col.aggregate([{$group:{_id:null,avgST:{$avg:{$sum:{$add:["$score.chinese","$score.math","$chinese.english"]}}}}}])
db.dept.insertMany([
{deptId: '001',count:2,salary:5000,employee:["zhangsan1","lisi1"]},
{deptId: '002',count:3,salary:6000,employee:["zhangsan2","lisi2","zhaowu"]},
{deptId: '003',count:4,salary:7000,employee:["zhangsan3","lisi3","zhaowu1","zhaowu2"]},
{deptId: '004',count:2,salary:4000,employee:["zhangsan4","lisi4"]},
{deptId: '005',count:3,salary:6000,employee:["zhangsan5","lisi5","zhaowu3"]},
{deptId: '006',count:3,salary:8000,employee:["zhangsan6","lisi6","zhaowu4"]},
{deptId: '007',count:2,salary:5000,employee:[]},
])

1、使用 $project 投影出需要的字段 排除 _id 字段,返回 count字段
产生一个新字段 newCount,它的值为原count字段的值 加 1

db.dept.aggregate([{$project:{_id:0,count:1,newCount:{$add:["$count",1]}}}])

2、使用$match过滤,显示count大于2的文档,并对salary字段升序排序

db.dept.aggregate([{$match:{count:{$gt:2}}},{$sort:{salary:1}}])

3、显示第3、4、5个文档信息

db.dept.aggregate([{$limit:5},{$skip:2}])

4、把数组employee中按照元素拆成多条文档显示,并显示原下标,数组为空时也显示该文档

db.dept.aggregate([{$unwind:"$employee"}])
db.dept.aggregate([{$unwind:{path:"$employee",includeArrayIndex:"emIndex",preserveNullAndEmptyArrays:true}}])

5、统计所有部门的平均薪资
db.dept.aggregate([{$group:{_id:null,avgSalary:{ a v g : " avg:" avg:"salary"}}}])
6、统计员工的总数

db.dept.aggregate([{$group:{_id:null,totalCount:{$sum:"$count"}}}])

7、统计人数最多的部门id

db.dept.aggregate([{$group:{_id:null,maxDept:{$max:"$count"}}}])
db.dept.find({count:4})
db.chinaPop.insert({_id:1,city:"shijiazhuang",pop:100000,provice:"hebei"})
db.chinaPop.insert({_id:2,city:"handan",pop:500000,provice:"hebei"})
db.chinaPop.insert({_id:3,city:"baoding",pop:300000,provice:"hebei"})

db.chinaPop.insert({_id:6,city:"qingdao",pop:100000,provice:"shandong"})
db.chinaPop.insert({_id:7,city:"jinan",pop:200000,provice:"shandong"})
db.chinaPop.insert({_id:8,city:"jining",pop:300000,provice:"shandong"})

db.chinaPop.insert({_id:11,city:"nanjing",pop:100000,provice:"jiangsu"})
db.chinaPop.insert({_id:12,city:"suzhou",pop:200000,provice:"jiangsu"})

解题思路:
1、统计人口数量超过50万的省
1)按照省进行分组,计算人口总和

db.chinaPop.aggregate([{$group:{_id:"$provice",total:{$sum:"$pop"}}}])

2)$match过滤大于50万人口的省

db.chinaPop.aggregate([{$group:{_id:"$provice",total:{$sum:"$pop"}}},
{$match:{total:{$gt:50000}}}])

2、计算每个省中每个城市的平均人口数量

db.chinaPop.aggregate([{$group:{_id:"$provice",
avg:{$avg:"$pop"}}}])

3、计算所有省的平均人口数量

db.chinaPop.aggregate([{$group:{_id:"$provice",total:{$sum:"$pop"}}},{$group:{_id:null,avgTotal:{$avg:"$total"}}}])

4、每个城市拥有的平均人口数量

db.chinaPop.aggregate([{$group:{_id:null, avg:{$avg:"$pop"}}}])

如果增加如下文档

db.chinaPop.insert({_id:4,city:"handan",pop:500000,provice:"hebei"})
db.chinaPop.insert({_id:5,city:"baoding",pop:300000,provice:"hebei"})

db.chinaPop.insert({_id:9,city:"jinan",pop:200000,provice:"shandong"})
db.chinaPop.insert({_id:10,city:"jining",pop:300000,provice:"shandong"})

db.chinaPop.insert({_id:13,city:"nanjing",pop:100000,provice:"jiangsu"})
db.chinaPop.insert({_id:14,city:"suzhou",pop:200000,provice:"jiangsu"})

1、计算每个省中每个城市的平均人口数量
1)根据省,城市分组,计算每个省,每个城市的人口总数

db.chinaPop.aggregate([{$group:{_id:{pro:"$provice",city:"$city"},
total:{$sum:"$pop"}}}])

2)根据省分组,计算平均值

db.chinaPop.aggregate([{$group:{_id:{pro:"$provice",city:"$city"},
total:{$sum:"$pop"}}},
{$group:{_id:"$_id.pro",cityAvg:{$avg:"$total"}}}
])

2、每个城市拥有的平均人口数量

db.chinaPop.aggregate([{$group:{_id:{pro:"$provice",city:"$city"},
total:{$sum:"$pop"}}},
{$group:{_id:null,cityAvg:{$avg:"$total"}}}
])

3、所有省的平均人口数量

db.chinaPop.aggregate([{$group:{_id:{pro:"$provice",city:"$city"},
total:{$sum:"$pop"}}},
{$group:{_id:"$_id.pro",totalCity:{$sum:"$total"}}},
{$group:{_id:null,avgPro:{$avg:"$totalCity"}}}
])

第二种简单思路

db.chinaPop.aggregate([{$group:{_id:"$provice",avgPop:{$avg:"pop"}}}])

4、每个省的人口最多和最少的城市
1)根据省,城市分组,计算每个省,每个城市的人口总数
2)按照人口总数升序
3)根据省分组,使用$first l a s t 找 出 人 口 最 多 、 最 少 的 城 市 , 4 ) 使 用 last找出人口最多、最少的城市, 4)使用 last4使project进行显示
{state:,biggestCity:{name:,pop:},smallestCity:{name:,pop:*}}

db.chinaPop.aggregate([
{$group:{_id:{provice:"$provice",city:"$city"},totalCityPop:{$sum:"$pop"}}},
{$sort:{totalCityPop:1}},
{$group:{_id:"$_id.provice",
biggestCity:{$last:"$_id.city"},
biggestPop:{$last:"$totalCityPop"},
smallestCity:{$first:"$_id.city"},
smallestPop:{$first:"$totalCityPop"}
}},
{$project:{
_id:0,
state:"$_id",
biggestCity:{name:"$biggestCity",pop:"$biggestPop"},
smallestCity:{name:"$smallestCity",pop:"$smallestPop"}
}}
])

5、统计所有的省名

db.chinaPop.distinct("provice")

聚合
1、统计人口数量超过1000万的州
1)按照州进行分组,计算人口总和

db.zipcodes.aggregate([{$group:{_id:"$state",totalPop:{$sum:"$pop"}}}])

2)$match过滤大于1000万人口的州

db.zipcodes.aggregate([{$group:{_id:"$state",totalPop:{$sum:"$pop"}}},{$match:{totalPop:{$gt:10*1000*1000}}}])

2、每个州,城市拥有的平均人口数量
1)根据州,城市分组,计算每个州,每个城市的人口总数

db.zipcodes.aggregate([
{$group:{_id:{state:"$state",city:"$city"},totalPop:{$sum:"$pop"}}}])

2)根据州分组,计算平均值市的人口总数

db.zipcodes.aggregate([
{$group:{_id:{state:"$state",city:"$city"},totalPop:{$sum:"$pop"}}},
{$group:{_id:"$_id.state",avgCitypop:{$avg:"$totalPop"}}},
{$sort:{_id:1}}
])

3、每个州的人口最多和最少的城市
1)根据州,城市分组,计算每个州,每个城市的人口总数
2)按照人口总数升序
3)根据州分组,使用$first l a s t 找 出 人 口 最 多 、 最 少 的 城 市 , 4 ) 使 用 last找出人口最多、最少的城市, 4)使用 last4使project进行显示

db.zipcodes.aggregate([
{$group:{_id:{state:"$state",city:"$city"},totalCityPop:{$sum:"$pop"}}},
{$sort:{totalCityPop:1}},
{$group:{_id:"$_id.state",
biggestCity:{$last:"$_id.city"},
biggestPop:{$last:"$totalCityPop"},
smallestCity:{$first:"$_id.city"},
smallestPop:{$first:"$totalCityPop"}
}},
{$project:{
_id:0,
state:"$_id",
biggestCity:{name:"$biggestCity",pop:"$biggestPop"},
smallestCity:{name:"$smallestCity",pop:"$smallestPop"}
}}
])

4、统计所有的州名

db.zipcodes.distinct("state")

MapReduce

db.test.find()
{ "_id" : ObjectId("5a1d45ab893253f4d2e4bf91"), "name" : "yy1", "age" : "22" }
{ "_id" : ObjectId("5a1d45b1893253f4d2e4bf92"), "name" : "yy2", "age" : "23" }
{ "_id" : ObjectId("5a1d45c5893253f4d2e4bf93"), "name" : "yy3", "age" : "24" }
{ "_id" : ObjectId("5a1d45d4893253f4d2e4bf94"), "name" : "yy5", "age" : "25" }
{ "_id" : ObjectId("5a1d45f7893253f4d2e4bf95"), "name" : "yy6", "age" : "26" }
{ "_id" : ObjectId("5a1d45ff893253f4d2e4bf96"), "name" : "yy4", "age" : "25" }

1、统计查询年龄大于23岁的姓名

方法1

map:
var m  = function(){if(this.age > 23) emit(this.age,{name:this.name})};

reduce:
var r = function(key,values){return JSON.stringify(values);}
或者:
var r = function(key,values){ var ret={names:values};return ret;}

生成结果集:

db.test.mapReduce(m,r,{out:"emp_res"})

方法2:过滤出来age>23的

db.test.mapReduce(
function(){emit(this.age,{name:this.name})},
function(key,values){var ret={name:values};return ret;},
{query:{age:{$gt:23}},
out:"emp_res"})

你可能感兴趣的:(mongodb,nosql,数据库)