(目录)
MongoDB的聚合操作是通过数据处理管道来实现的,一次操作可以使用多个管道来处理数据文档。管道是有顺序的,会依序将管道的结果传到下一个管道中继续处理,进而显示最后的结果。 聚合操作可以实现分组、排序、数值运算、条件筛选、多表关联查询等功能,可以在分片环境下使用。
语法
db.collection.aggregate(pipeline, options)
聚合 $group
$group
用于对文档中的特定字段进行分组,还可以搭配以下操作符,对分组的结果进行计算
与$group搭配的常用操作符
- $sum:求和
- $avg: 计算平均值
- $first: 显示指定字段的第一个值
- $last: 显示指定字段的最后一个值
- $max: 最大值
- $min: 最小值
- $push: 以数组的方式显示指定字段
- $addToSet: 以数组的方式,显示字段不重复的值
语法格式
{
$group:
{
"_id": $<分组的字段名称>,
<显示结果的名称>: { <操作符> : $<计算的字段> },
...
}
}
示例
# 文档中的数据如下
db.grades.find().pretty()
{
"_id" : ObjectId("56d5f7eb604eb380b0d8d8d0"),
"student_id" : 0,
"scores" : [
{
"type" : "exam",
"score" : 84.72636832669608
},
{
"type" : "quiz",
"score" : 7.8865616909793435
},
{
"type" : "homework",
"score" : 22.860114572528147
},
{
"type" : "homework",
"score" : 80.85669686147487
}
],
"class_id" : 149
}
{
"_id" : ObjectId("56d5f7eb604eb380b0d8d8d4"),
"student_id" : 0,
"scores" : [
{
"type" : "exam",
"score" : 20.2317531451231
},
{
"type" : "quiz",
"score" : 97.5705578455598
},
{
"type" : "homework",
"score" : 15.645222266486435
},
{
"type" : "homework",
"score" : 21.03830826968486
}
],
"class_id" : 57
}
...}
# 用student_id分组,以数组方式显示class_id字段
db.grades.aggregate(
[
{$group:
{"_id":"$student_id","class_id":{$push:"$class_id"} }
} ]
)
# 结果
{ "_id" : 249, "class_id" : [ 489, 301, 242, 478, 1, 69, 433, 67, 180, 496 ] }
{ "_id" : 881, "class_id" : [ 296, 419, 408, 9, 24, 51, 379, 412, 500, 240 ] }
...
显示字段 $project
$project
可以用来设置显示哪些字段,将要显示的字段设定为1,其余字段默认不显示(_id
字段默认显示,要不显示需要设置为0)
示例 查询grades集合,只显示student_id和class_id字段
db.grades.aggregate([{$project:{_id:0,student_id:1,class_id:1}}])
{ "student_id" : 0, "class_id" : 149 }
{ "student_id" : 0, "class_id" : 57 }
{ "student_id" : 0, "class_id" : 331 }
...
$project搭配其他操作符
$project
除了显示特定的字段外,还可以搭配聚合的其他操作符使用
db.collection.aggregate([
{$project:
{
<显示的字段名称1>:{<操作符1>:<操作符条件1>},
<显示的字段名称2>:{<操作符2>:<操作符条件2>},
...
}
}
])
截取字符串中指定的字符 $substr
可以用来截取字符串中指定数量的字符,有三个参数
- 字符串:要截取的字符串
- 起始参数:从那个字符开始截取。指定为0,表示从第一个字符开始截取,1表示从第二个字符开始截取
- 长度参数:要截取的字符长度。如果设置为负数,表示截取到字符串结束
#截取start station name字段的前5个字符
db.trips.aggregate({
$project:
{
"start station name":{$substr:["$start station name",0,5]}
}
})
条件判断 $switch
可以对指定字段进行一系列的条件判断。如果字段符合条件,则显示对应的结果(类似SQL中的CASE WHEN THEN
)
db.grades.aggregate([
{$project:
{"scorelevel":{ $switch: {score:[
{ case:{$gt:["$score",80]},then:"good"},
{ case:{$gt:["$score",60]},then:"nice"} ] ,
default:"fail" }}}}
])
查询指定字符在文档中的位置 $indexOfBytes
可以查询出指定字符出现在文档字符串中的第几个位置,包含4个参数
- 字符串:文档中字符串形式的字段
- 指定字符:要查询位置的字符串
- 起始位置:可选,从文档中的哪个位置开始查询,从0开始计数
- 结束位置:可选,在在文档中的哪个位置结束查询
# 查询start station name中St字符串在第几个位置,从第二个字符开始查询,查到第三十个字符结束
db.trips.aggregate([{
$project:{"index of start station name":{$indexOfBytes:["$start station name","St",1,29]}}}
])
# 结果为-1表示没有查询到指定的字符串
计算字符串的长度 $strLenBytes
计算字符串的长度。一般情况下,英文一个字母是一个byte,中文一个汉字是3个byte。
db.trips.aggregate([{
$project:{"start station name":{$strLenBytes:"$start station name"}}}
])
比较两个字符串间的大小 $strcasecmp
用此操作符可以比较两个字符串之间的大小,结果如下:
- 1 第一个字符串大于第二个字符串
- 0 第一个字符串等于第二个字符串
- -1 第一个字符串小于第二个字符串
db.trips.aggregate([{
$project:
{
"scmp":{$strcasecmp:["start station name","$end station name"]}
}
}])
转换字符串字母大小写 $toLower $toUpper
- $toLower 将字符串统一转换为小写
- $toUpper 将字符串统一转换为大写
db.trips.aggregate([{
$project:
{
"lower":{$toLower:"$start station name"},
"upper":{$toUpper:"$start station name"},
"_id":0
}
}])
# 结果
{ "lower" : "e 31 st & 3 ave", "upper" : "E 31 ST & 3 AVE" }
{ "lower" : "howard st & centre st", "upper" : "HOWARD ST & CENTRE ST" }
合并拆分字符串 $concat $split
- concat合并字符串
- split 将字符串按照指定的字符拆分成数组
db.trips.aggregate([{
$project:
{
"station name":{$concat:["start at:","$start station name"," end at:","$end station name"]},
"start split by &":{$split:["$start station name","&"]},
"_id":0
}
}]).pretty()
# 结果
{
"station name" : "start at:E 31 St & 3 Ave end at:Broadway & W 32 St",
"start split by &" : [
"E 31 St ",
" 3 Ave"
]
}
{
"station name" : "start at:Howard St & Centre St end at:South End Ave & Liberty St",
"start split by &" : [
"Howard St ",
" Centre St"
]
}
加减乘除 $add $subtract $multiply $divide $mod
- $add 对数值或日期类型的字段进行加法运算
- $subtract 对数值或日志类型的字段进行减法运算
- $multiply 对数值类型的字段进行乘法运算
- $divide 对数值类型的字段进行除法运算
- $mod 对数值类型的字段进行除法取余数运算
db.trips.aggregate([{
$project:
{
"add":{$add:["$start time",1]}, # 日期加1单位是毫秒
"subtract":{$subtract:["$stop time",new Date("2022-02-25")]},
"multiply":{$multiply:["$tripduration",10]},
"divide":{$divide:["$tripduration",10]},
"mod":{$mod:["$tripduration",10]},
"_id":0
}
}])
# 结果
{
"add" : ISODate("2016-01-01T00:00:45.001Z"),
"subtract" : NumberLong("-194140376000"),
"multiply" : 3790,
"divide" : 37.9,
"mod" : 9
}
{
"add" : ISODate("2016-01-01T00:01:06.001Z"),
"subtract" : NumberLong("-194139844000"),
"multiply" : 8890,
"divide" : 88.9,
"mod" : 9
}
取得时间内容 $year $month $week $hour $minute $second $milisecond $dayOfYear $dayOfMonth $dayOfWeek
- $year 只显示日期格式字段值中的年份
- $month 只显示日期格式字段值中的月份
- $week 显示日期格式字段值是这一年的第几周,0-53,一年之中的第一个星期是第0周
- $hour 只显示日期格式字段值中的小时数
- $minute 只显示日期格式字段值中的分钟数
- $second 只显示日期格式字段值中的秒数
- $millisecond 只显示日期格式字段值中的毫秒数
- $dayOfYear 显示日期格式字段值是这一年的第几天
- $dayOfMonth 显示日期格式字段值是这个月的第几天
- $dayOfWeek 显示日期格式字段值是一周的第几天(星期日是第1天,星日六是第7天)
db.trips.aggregate([{
$project:
{
"year":{$year:new Date("2022-02-25 10:44:07")},
"month":{$month:new Date("2022-02-25 10:44:07")},
"week":{$week:new Date("2022-02-25 10:44:07")},
"hour":{$hour:new Date("2022-02-25 10:44:07")},
"minute":{$minute:new Date("2022-02-25 10:44:07")},
"second":{$second:new Date("2022-02-25 10:44:07")},
"millisecond":{$millisecond:new Date("2022-02-25 10:44:07")},
"dayOfYear":{$dayOfYear:new Date("2022-02-25 10:44:07")},
"dayOfMonth":{$dayOfMonth:new Date("2022-02-25 10:44:07")},
"dayOfWeek":{$dayOfWeek:new Date("2022-02-25 10:44:07")},
"_id":0
}
}]).pretty()
# 结果
{
"year" : 2022,
"month" : 2,
"week" : 8,
"hour" : 18, #有时区的转换
"minute" : 44,
"second" : 7,
"millisecond" : 0,
"dayOfYear" : 56,
"dayOfMonth" : 25,
"dayOfWeek" : 6
}
分页常用操作符 $sort $skip $limit
- $sort 对所有文档进行排序,1为升序,-1为降序
- $skip 在显示文档时跳过指定数量的文档
- $limit 限制显示的文档数量
# 以start time升序,stop time降序显示
db.trips.aggregate([
{$project:{"start station name":1,"end station name":1,"_id":0}},
{$sort:{"start time":1,"stop time":-1}},
{$skip:1},
{$limit:2}
]).pretty()
条件筛选 $match
$match
可以搭配条件查询或正则表达式来筛选文档
#
db.trips.aggregate([
{$match: {$and:[
{"start station id":{$gt:200}},
{"end station id":{$lt:400}},
{"start station name":{$regex:/23/}}
]}}
]).pretty()
多表关联 $lookup
用$lookup
操作符可以查找出集合中与另一个集合条件匹配的文档,类似于关系数据库中的join
操作。需要使用以下参数
- from 想要关联的另一个集合
- localField: 集合中需关联的键
- foreignField: 与另一个集合关联的键
- as: 关联后会将另一个集合的数据嵌入至此字段下
db.trips.aggregate([
{$lookup:{
from:"trips",
localField:"start station id",
foreignField:"end station id",
as:"All"
}},
{$match: {$and:[
{"start station id":{$gt:200}},
{"end station id":{$lt:400}}
]}},
{$project:{"All":0}},
{$limit:2}
]).pretty()
计算文档数量 $count
db.trips.aggregate([
{$match: {$and:[
{"start station id":{$gt:200}},
{"end station id":{$lt:400}}
]}},
{$count:"count"}
]).pretty()
展开数组 $unwind
用$unwind
可以将文档中数组形成的数据拆分成多个文档。如果指定的字段不存在,则不会进行拆分,也不会显示结果
参数
- path: 执行要拆分的数组字段,以
$
开头 - includeArrayIndex: 可选参数,包含字段在原数组中的位置
- preserveNullAndEmptyArrays: 可选参数,是否输入未拆分的文档,默认为false
db.grades.aggregate([
{$unwind:
{
path:"$scores",
includeArrayIndex:"arrayIndex",
preserveNullAndEmptyArrays:true
}
}
])
结果汇入新表 $out
使用$out
可以将呼和出来的结果放入到一个指定的集合中。如果指定的集合不存在,则会创建新集合,如果指定的集合已经存在,则会覆盖写入到此集合。但如果原有的集合中存在索引,而使用$out
插入的文档不符合索引要求,则会写入失败。
db.grades.aggregate([
{$unwind:
{
path:"$scores",
includeArrayIndex:"arrayIndex",
preserveNullAndEmptyArrays:true
}
},
{$project:{"_id":0}},
{$out:"grades_by_scores"}
])