Appearance
聚合操作
- 聚合操作就是通过一个方法完成一系列操作
- 在聚合操作中,每一个操作我们称之为一个阶段,聚合操作会将上一个阶段结果传给下一个阶段,继续处理, 所有阶段处理完毕会返回一个新的结果集给我们
聚合操作格式:
db.集合名称.aggregate(<pipeline>,<options>)
- pipeline:定义每个阶段操作
- options:聚合操作额外配置
$project
$project: 对输入文档进行再次投影
作用:按照我们需要的格式生成结果集
格式:{$project:{<field>:<value>}}
{
name:{firstName:'name',lastName:'Zhu'},
age:18,
book:{name:'html',price:12}
}
db.person.aggregate([
{
$project:{_id:0,myName:'$name.firstName'} #不显示_id,第二个参数相当于mysql中更改修改字段的名称
}
])
注意点:
聚合操作不会修改原有的文档,而是返回一个新的文档给我们
如果在$project聚合操作中,是用来原有文档中不存在的字段,那么自动使用null填充
db.person.aggregate([
{
$project:{fullName:['$name.firstName','$name.middleName','$name.lastName']}# 中间的值并不存在,所以会直接给一个null值
}
])
$match
$match: 和find方法中的第一个参数一样,用于筛选符合条件的文档
格式:{$match:{<query>}}
{
name:{firstName:'name',lastName:'Zhu'},
age:18,
book:{name:'html',price:12}
}
db.person.aggregate([
{
$match:{
'name.firstName':'name'
}
},
{
$project:{
_id:0,
fullName:['$name.firstName','$name.lastName'],
myAge:'$age'
}
}
])
- 以上语句的执行顺序(按管道的顺序由上至下执行)
先查询符合条件的文档
再对符合条件的文档进行处理,修改符合文档的名称
$limit和$skip
$limit : 和游标的limit方法一样,用于指定获取几个文档
格式:{$limit:<number>}
$skip : 和游标的skip一样,用于指定跳过几个文档
格式:{$skip:<number>}
db.person.aggregate([
{$skip:1},
{$limit:1},
{$project:{
_id:0,
fullName:['$name.firstName','$name.lastName'],
myAge:'$age'
}}
])
注意点
在聚合操作中$limit和$skip是不同的阶段,所以要写到不同的对象中
$unwind
$unwind: 展开数组字段
{
name:{firstName:'name',lastName:'Zhu'},
age:18,
book:{name:'html',price:12},
tags:['html','js']
}
db.person.aggregate([
{
$unwind:{
path:'$tags', #需要展开的字段
includeArrayIndex:'index' #展开字段的第几个值
}
}
])
结果集为两个:
{
tags:'html',
index:NumberLong(0)
}
{
tags:'js',
index:NumberLong(1)
}
注意点
如果原有文档中没有需要操作的数组字段/数组字段没有元素/null,那么在$unwind阶段,会自动将这些数据过滤掉
如果不需要过滤掉需要添加一个参数:preserveNullAndEmptyArrays:true
$sort
$sort:和文档游标sort方法一致,对文档进行排序
格式:{$sort:{<field>:1|-1}}
db.集合文档.aggregate([
{
$sort:{
age:1
}
}
])
$lookup
$lookup : 用来做关联查询的
第一种格式
格式:
{
$lookup:{
from:关联集合名称,
localField:当亲集合中的字段名称
foreignField:关联集合中的字段名称,
as:输出字段名的名称
}
}
db.person.insert(
[
{
name:{firstName:'name',lastName:'Zhu'},
age:18,
books:['html','js']
},
{
name:{firstName:'name1',lastName:'Zhu1'},
age:19,
books:['vue']
},
{
name:{firstName:'name2',lastName:'Zhu2'},
age:20,
books:[]
}
]
)
db.books.insert([
{name:'html',price:88},
{name:'js',price:66},
{name:'vue',price:99}
])
# 取出每个拥有的书对应的书的详情
db.person.aggregate([
{
$unwind:{path:'$books'}
},
{$lookup:
{
from:'books',# 集合名称
localField:'books',#person字段名称
foreifnField:'name',
as:'data'
}
}
])
第二种格式
格式:
{
$lookup:{
from:关联集合名称,
let:{<var_1>:<expression>,...},# 定义给关联集合的聚合操作使用的当前集合的常量
pipeline:[<pipline to execute on the collection to join>],#关联集合的聚合操作
as :输出字段的名称
}
}
db.person.aggregate([
{$lookup:{
from:'books',
let:{bks:'$books'},
pipeline:[
{$match:{
$expr:{
$and:[
{$gte:['$price',99]},
{$in:['$name','$$bks']} #两个$$代表定义的常量
]
}
}}
],
as:'data'
}}
])
注意点:
在第二种$lookup阶段的第二种格式中,默认情况下是无相关查询
$group
$group:对文档进行分组
格式:
{
$group:{
_id:<expression>,
<field1>:{<accumulator1>:<expression>},
.......
}
}
_id : 定义分组的规则
<field> : 定义新字段
db.person.insert([
{name:'zs', age:10, city:'北京'},
{name:'ls', age:20, city:'上海'},
{name:'ww', age:30, city:'北京'},
{name:'zl', age:40, city:'上海'},
{name:'lnj', age:50, city:'北京'},
{name:'jjj', age:60, city:'广州'},
])
db.person.aggregate([
{$group:{
_id:'$city',
totalAge:{$sum:'$age'},
avgAge:{$avg:'$age'},
minAge:{$min:'$age'},
maxAge:{$max:'$age'},
totalName:{$push:'$name'}
}}
])
$out
$out: 前面阶段处理完的文档写入一个新的集合中
{$out:'新的集合名称'}
db.person.aggregate([
{$group:{
_id:'$city',
totalAge:{$sum:'$age'},
avgAge:{$avg:'$age'},
minAge:{$min:'$age'},
maxAge:{$max:'$age'},
totalName:{$push:'$name'}
}},
{
$out:'newPerson'
}
])
注意点:
如果通过$out将结果集写入到一个新的集合中,如果这个集合不存在,那么就创建一个新的集合。 如果存在集合,会覆盖掉原来的集合的文档
聚合操作的额外配置——allowDiskUse
<options>:
{allowDiskUse:<boolean>}
allowDiskUse默认取值是false,默认情况下管道阶段占用的内存不能超过100M,如果超过100M就会报错, 如果需要处理的数据比较多,聚合操作使用的内存可能超过100M,那么我们可以将 allowDiskUse设置为true,如果allowDiskUse设置为true,那么一旦超过100M 就会将操作的数据写入到临时的文件中,然后再继续操作
表达式
字段路径表达式
$<field>:使用$来指示字段路径
$<field>.<sub-field>:使用$和.来指定内嵌文档字段路径
例如:
$name
系统变量表达式
$$CURRENT:表示当前操作的文档
$$CURRENT.name 等价于 $name
常量表达式
将字段表达式变成一个常量字符串
使'$age'不输出值直接输出$age字符串
$literal:<value>:表示常量<value>
$literal:'$name' :表示常量字符串$name
数据类型转换操作符
MongoDB对于文档的格式并没有强制性的要求,同一个集合中存储的文档,字段的个数和数据类型都可以不同 对于文档的格式没有强制性的要求,这是MongoDB的一大优势,但是同时也增加了数据消费端的使用难度 因为我们在使用数据的时候,有可能同一个字段取出来的数据类型是不同的,这样非常不利于我们后续操作,所以 也正是因为如此,MongoDB在4.0中推出了$convert数据类型转换操作符
- 通过$convert数据类型转换操作符,我们可以将不同的数据类型转换成相同的数据类型 以便于以后在使用数据的过程中能够统一对数据进行处理
格式:
{$convert:{
input:'需要转换的字段',
to:'转换之后的数据类型',
onError:'不支持的转换类型',
onNull:'没有需要转换的数据'
}}
db.person.insert([
{name:'zs', timestamp:ISODate('2020-08-09T11:23:34.733Z')},
{name:'ls', timestamp:'2021-02-14 12:00:06 +0800 '},
{name:'ww', timestamp:' 2023-04-01T12:00:00Z'},
{name:'zl', timestamp:'1587009270000'},
{name:'it666', timestamp:'Sunday'},
{name:'itzb'},
])
db.person.aggregate(
[
{$project:{
_id:0,
name:'$name',
time:{
$convert:{
input:'$timestamp',
to:'date',
onError:'不能转换',
onNull:'没有时间字段'
}
}
}
}
]
)