(目录)

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"}
])