文章目录
第六章 聚合
聚合框架是MongoDB的高级查询语言,类似于关系数据库中的groupBy语句。
聚合框架概览
为调用聚合框架就要定义一个管道。聚合管道里的每一步输出做为下一步的输入。每一步都在输入文档执行单个操作并生成输出文档。
聚合管道包含下面一个部分
- $project 指定输出文档里的字段; 类似SQL的select
- $match 过滤要处理的文档; 类似SQL的where条件
- $limit 限制传递给下一步文档的数量
- $skip 跳过一定文档的数量
- $unwind 扩展数据,为每个数据元素生成一个输出文档
- $group 根据key分组 ;类似SQL的group by
- $sort 排序文档
- $geoNear 选择某个地理位置附近的文档
- $out 把管道的结构输出到一个集合
- $redact 控制特定数据的访问
样例:
db.products.aggregate( [ {$match:...},{$group:...},{$sort:...}] )
测试数据
db.student.insert({name:'zhangsan',age:14,class:'cla1',likes:['足球','羽毛球']})
db.student.insert({name:'lisi',age:16,class:'cla1',likes:['足球','篮球']})
db.student.insert({name:'wangwu',age:15,class:'cla2',likes:['羽毛球','篮球']})
db.student.insert({name:'zhaoliu',age:16,class:'cla2',likes:['乒乓球','跑步']})
db.student.insert({name:'tianqi',age:16,class:'cla1',likes:['乒乓球','足球']})
练习
统计每个班级的人数
db.student.aggregate( [
{$group:
{ _id:'$class' , count:{$sum:1} }
}
])
{ "_id" : "cla2", "count" : 2 }
{ "_id" : "cla1", "count" : 3 }
注意:使用$group时,必须指定分组字段名,且分组字段的名字只能是_id ,如果为如果为其它的名字会报错
$group-统计cla1的总人数
db.student.aggregate([
{$match:{class:'cla1'}},
{$group:{_id:'$class',count:{$sum:1}}}
])
{ "_id" : "cla1", "count" : 3 }
根据$match获取到所有cla1的文档,之后统计cla1的文档个数
$avg-统计每个班级的人数,及年龄的平均值
db.student.aggregate([
{$group:{_id:'$class', count:{$sum:1}, avg:{$avg:'$age'}}}
])
{ "_id" : "cla2", "count" : 2, "avg" : 15.5 }
{ "_id" : "cla1", "count" : 3, "avg" : 15.333333333333334 }
$out-将管道结果保存到集合中
db.student.aggregate([
{$group:{ _id:'$age', sum:{$sum:1} }} ,
{$out:'result'}
])
将聚合结果保存到result集合中
$project 过滤可以传给下一步的字段
db.student.aggregate([
{$project :{name:1, age:1, _id:0} }
])
$unwind 扩展数组-统计每个爱好的人数
db.student.aggregate([
{$project:{likes:1}}, 只将likes传递到一个步骤,减少数据量优化性能
{$unwind:'$likes'}, 将likes数组的每个元素扩展为一个文档
{$group:{_id:'$likes',sum:{$sum:1}}} ,根据likes分组,并统计每个元素的个数。
{$sort:{sum:-1}} 根据数量排序
])
{ "_id" : "跑步", "sum" : 1 }
{ "_id" : "足球", "sum" : 3 }
{ "_id" : "羽毛球", "sum" : 2 }
{ "_id" : "篮球", "sum" : 2 }
{ "_id" : "乒乓球", "sum" : 2 }
$group函数
- $addToSet 求组内所有值的数组,去重
- $ first 求组里的第一个值,只有前缀$sort才有意义
- $ last 求组里的最后一个值,只有前缀$sort才有意义
- $max 求组内字段的最大值
- $min 求组内字段的最小值
- $avg 求组内 字段的平均值
- $push 求组内所有值的数组,不去重
- $sum 求组内所有值的和
db.student.aggregate([
{$group:
{_id:'$class',
avg_age:{$avg:'$age'}, 均值
max_age:{$max:'$age'}, 最大值
min_age:{$min:'$age'}, 最小值
sum:{$sum:1}, 和
first_age:{$first:'$age'}, 第一个值
last_age:{$last:'$age'}, 最后一个值
set_age:{$addToSet:'$age'}, 所有年龄 去重
push_age:{$push:'$age'} 所有年龄 不去重
}
}
]).pretty()
结果
{
"_id" : "cla2",
"avg_age" : 15.5,
"max_age" : 16,
"min_age" : 15,
"sum" : 2,
"first_age" : 15,
"last_age" : 16,
"set_age" : [
16,
15
],
"push_age" : [
15,
16
]
}
{
"_id" : "cla1",
"avg_age" : 15.333333333333334,
"max_age" : 16,
"min_age" : 14,
"sum" : 3,
"first_age" : 14,
"last_age" : 16,
"set_age" : [
16,
14
],
"push_age" : [
14,
16,
16
]
}
文档重塑
mongodb的聚合管道包含许多可以用来重塑文档的函数,因此可以在原有的文档中添加新的字段。通常会与$project一起使用这些函数。
除了重命名或者重新构建文档字段外,也可以使用不同的重塑函数来创建新的字段。重塑函数根据处理类型的不同进行分组:
字符串、算数运算、日期、逻辑、集合、其他类型。
字符串函数
db.student.aggregate([
{$project:
{name:{$concat:['$name','_A','_B']},
subName:{$substr:['$name',0,3]},
upperName:{$toUpper:'$name'}
}
}
])
{ "_id" : ObjectId("5d1fea22b2b6226b3223e4ba"), "name" : "zhangsan_A_B", "subName" : "zha", "upperName" : "ZHANGSAN" }
{ "_id" : ObjectId("5d1fea35b2b6226b3223e4bb"), "name" : "lisi_A_B", "subName" : "lis", "upperName" : "LISI" }
{ "_id" : ObjectId("5d1fea4db2b6226b3223e4bc"), "name" : "wangwu_A_B", "subName" : "wan", "upperName" : "WANGWU" }
{ "_id" : ObjectId("5d1fea7bb2b6226b3223e4bd"), "name" : "zhaoliu_A_B", "subName" : "zha", "upperName" : "ZHAOLIU" }
{ "_id" : ObjectId("5d1fea8bb2b6226b3223e4be"), "name" : "tianqi_A_B", "subName" : "tia", "upperName" : "TIANQI" }
算数运算符
日期函数
逻辑运算符
集合运算符
其他运算符
聚合函数性能
聚合管道性能的关键点
- 尽早在管道里尝试减少文档的数量和大小 ;减少数量$match,减少字段 $project
- 索引只能用于 m a t c h 和 match和 match和sort操作,而且可以大大加速查询
- 使用 m a t c h 和 match和 match和sort 之外的操作符不能使用索引
- 如果是sharding部署模式,则 m a t c h 和 match和 match和project会在单独的分片上执行。
聚合管道的选项参数
完整的聚合函数的参数
db.collection.aggregate(pipeline,additionalOptions);
pipe为之前的聚合管道操作数据;additionalOptions 为选项参数;如下
{explain:true, allowDiskUse:true, cursor:{batchSize:n}}
- explain 显示管道的执行计划
- allowDiskUse 使用磁盘存储数据
当我们处理超大型的集合时,管道返回了超多Mongo RAM 内存限制的100MB数据,就会报错,可以通过指定allowDiskUse:true解决问题 - cursor 指定初始批处理的大小
第7章 更新、原子操作、删除
单个文档更新
修改zhangsan的年龄和爱好
db.student.update(
{name:'zhangsan'},
{ $set:{age:20}, $addToSet:{likes:'aa'}}
)
多个文档更新
{multi:true})
每个同学都喜欢上网
db.student.update(
{},
{$addToSet:{likes:'上网'}},
{multi:true})
WriteResult({ "nMatched" : 5, "nUpserted" : 0, "nModified" : 5 })
upserts 文档存在时更新,不存在时插入
db.student.update(
{name:'jingba'},
{$set:{age:8}},
{upsert:true})
更新操作符
- $inc 可以增加或减少一个数值
- $unset 删除文档的字段
- $rename 修改文档key的名字
db.student.update(
{name:'jingba'},
{ $inc:{age:1},
$rename:{'name':'rename'}
})
$setOnInsert 在upsert中,如果数据不存在,可以设置字段的初始值。
db.student.update(
{name:'hejiu'},
{ $inc:{age:1},
$setOnInsert:{name:'hejiu',likes:['aa','bb']} }, #name:'hejiu' 不存在时,设置初始值
{upsert:true})
数组操作符
$push $each
在数组中插入一个元素
db.student.update(
{name:'zhangsan'},
{$push:{likes:'bb'}})
在数组中插入多个元素
db.student.update(
{name:'zhangsan'},
{$push:{likes:{$each:['c','d','e']}}})
$slice 限制数组元素的个数
db.temp.insert({_id:326,tmp:[92,93,94]});
db.temp.update(
{_id:326},
{$push:
{'tmp':{
$each:[95,96],
$slice:-4}
}
})
{ "_id" : 326, "tmp" : [ 93, 94, 95, 96 ] }
$slice:-4 从数组的尾部开始保留4个元素
$slice:4 从数组的头部开始保留4个元素
$sort对结合元素进行排序
db.temps.insert({_id:300,temps:[{day:6,temp:90},{day:5,temp:95}]});
db.temps.update(
{},
{$push:
{temps:{
$each:[{day:7,temp:92}],
$slice:-2,
$sort:{day:1}
}
}
}
)
$pop 删除元素 通过元素的索引删除
db.temps.update(
... {},
... {$pop:{'temps':1}})
1从尾部删除 -1从头部删除
$pull 删除元素 ,通过元素的值删除
findAndModify命令
db.student.findAndModify({
query:{name:'zhangsan'},
update:{$set:{age:18}}
});
参数
删除
db.student.remove({name:'zhangsan'})
并发、原子性和隔离
- 在mongodb3.0之后支持了文档级别的锁,降低了锁的粒度,提高了并发性能
- mongodb的插入、修改和删除都需要占用写锁。
- mongo的插入一般效率较高,修改和删除可能会影响整个结合,可能需要很长时间完成,为了提高并发性能,mongo允许这种长时间的操作为其他读写让路。当一个操作屈服时,它会暂停释放锁,后面再重新启动。
- 当更新和删除文档时,这种退让机制可能是不期望出现的。我们可以设置$isolated来保持操作独立,避免让路。
更新性能
1.尽量避免使用文档替换方式更新,如果替换后的文档较大,会发生文档移动影响更新性能
2.尽量使用字段替换的方式修改文档