1.测试数据
emp集合
db.emp.insert(
[
{
_id: 1,
name: "tom",
age: 13,
likes: ["basketball", "football"],
sex: "F",
dept_id: 1
},
{
_id: 2,
name: "jack",
age: 14,
likes: ["games", "douyin"],
sex: "M",
dept_id: 2
},
{
_id: 3,
name: "amy",
age: 23,
likes: ["code", "basketball"],
sex: "F",
dept_id: 1
},
{
_id: 4,
name: "musk",
age: 42,
likes: ["car", "space"],
sex: "F",
dept_id: 2
},
{
_id: 5,
name: "paul",
age: 34,
likes: [],
sex: "F",
dept_id: 2
}
]
)
dept集合
db.dept.insert(
[
{
_id: 1,
d_name: "研发部"
},
{
_id: 2,
d_name: "运维部"
}
]
)
2.简单演示
一个简单的聚合操作如下,这个聚合操作会经过两个阶段的数据处理:
- 第一个管道阶段为 $match:会筛选出所有
sex值为F 并且 dept_id 为2
的文档,然后输出到下一个管道操作中; - 第二个管道阶段为 $project:用于定义返回的字段内容,这里返回 _id name sex dept_id 以及自定义的字段名emp_name取值为name的值
db.emp.aggregate([
{
$match: {
sex: "F", //匹配 sex = F的
dept_id: 2 //匹配 dept_id = 2的
}
},
{
$project: {
//映射,1显示,0不显示
_id: 1,
name: 1,
sex: 1,
dept_id: 1,
}
}
])
运行结果
Java代码
@Test
void agg() {
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.match(Criteria.where("sex").is("F").and("dept_id").is(2)),
Aggregation.project("_id", "name", "sex", "dept_id")
);
//参数一 Aggregation
//参数二 聚合名
//参数三 对应的类对象
AggregationResults<Emp> emp = mongoTemplate.aggregate(aggregation, "emp", Emp.class);
//调用getMappedResults()方法返回结果集
List<Emp> mappedResults = emp.getMappedResults();
}
3.$Match
$match 主要用于筛选符合条件的数据,通常应该把 $match 放在尽量靠前的位置,这时候它会利用索引来优化查询,同时还可以降低后续阶段所需要处理的数据量。示例如下:
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.match(查询条件)
);
4.$Project
$project 主要用于定义需要返回的字段,1 代表包含该字段,0 代表不包含。
//定义只返回k1和k2字段的值,注意这里必须填集合中的字段名
Aggregation.project("k1", "k2")
5.$group
$group 管道阶段和大多数关系型数据库中的 group by
字句功能类似,都是用于分组计算。示例如下:
db.emp.aggregate(
{ //根据dept_id分组,查询总年龄和平均年龄
$group:{
"_id": "$dept_id",
"age_sum": {$sum: "$age"},
"age_avg": {$avg: "$age"}
}
}
)
输出
Java代码
@Test
void agg() {
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.group("dept_id").sum("age").as("age_sum").avg("age").as("age_avg"),
Aggregation.sort(Sort.by(Sort.Order.asc("_id")))
);
//第三个参数,指定数据每条数据以什么类型返回,这里显然以Map形式返回。
AggregationResults<Map> emp = mongoTemplate.aggregate(aggregation, "emp", Map.class);
List<Map> mappedResults = emp.getMappedResults();
for (Map mappedResult : mappedResults) {
System.out.println(mappedResult);
}
}
/*
* 输出结果
* {_id=1.0, age_sum=36.0, age_avg=18.0} //_id就是 dept_id(这里根据dept_id分组)
* {_id=2.0, age_sum=90.0, age_avg=30.0}
*/
参考更多group后的聚合函数
6.$unwind
$unwind 将文档按照数组中
的每一个元素进行拆分
语法格式
{
$unwind:
{
path: <field path>,
includeArrayIndex: <string>,
preserveNullAndEmptyArrays: <boolean>
}
}
- path:用于展开的数组字段;
- includeArrayIndex:用于显示对应元素在原数组的位置信息;
- preserveNullAndEmptyArrays:如果用于展开的字段值为 null 或空数组时,则对应的文档不会被输出到下一阶段。如果想要输出到下一阶段则需要将该属性设置为 true。示例语句如下:
示例
db.emp.aggregate(
{
$unwind:{
path: "$likes",
includeArrayIndex: "arrayIndex",
preserveNullAndEmptyArrays: true
}
}
)
结果
注意:如果 preserveNullAndEmptyArrays 的值为 false 或者没有设置,则 5-paul 这条数据不会被输出:
Java代码
@Test
void agg() {
Aggregation aggregation = Aggregation.newAggregation(
//默认 没有第二个参数,第三个参数为false
Aggregation.unwind("likes")
);
AggregationResults<Emp> emp = mongoTemplate.aggregate(aggregation, "emp", Emp.class);
List<Emp> mappedResults = emp.getMappedResults();
for (Emp mappedResult : mappedResults) {
System.out.println(mappedResult);
}
}
/*
Emp{id=1, name='tom', age=13, lists=[basketball], sex='F', deptId=1}
Emp{id=1, name='tom', age=13, lists=[football], sex='F', deptId=1}
Emp{id=2, name='jack', age=14, lists=[games], sex='M', deptId=2}
Emp{id=2, name='jack', age=14, lists=[douyin], sex='M', deptId=2}
Emp{id=3, name='amy', age=23, lists=[code], sex='F', deptId=1}
Emp{id=3, name='amy', age=23, lists=[basketball], sex='F', deptId=1}
Emp{id=4, name='musk', age=42, lists=[car], sex='M', deptId=2}
Emp{id=4, name='musk', age=42, lists=[space], sex='M', deptId=2}
*/
7.$sort&skip&limit
$sort
主要用于排序操作,需要注意的是如果可以,应当尽量将该操作放置在管道的第一阶段,从而可以利用索引进行排序,否则就需要使用内存进行排序,这时排序操作就会变得相当昂贵,需要额外消耗内存和计算资源。示例如下$limit
限制返回文档的数量。$skip
跳过一定数量的文档。
db.emp.aggregate(
[
{$sort:{age:1}}, //按照年龄升序排序
{$skip:2}, //跳过前2条数就
{$limit:2} //显示2条数据
]
)
Java代码
@Test
void agg() {
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.sort(Sort.by(Sort.Order.asc("age"))),
Aggregation.skip(2),
Aggregation.limit(2)
);
AggregationResults<Emp> emp = mongoTemplate.aggregate(aggregation, "emp", Emp.class);
List<Emp> mappedResults = emp.getMappedResults();
for (Emp mappedResult : mappedResults) {
System.out.println(mappedResult);
}
}
/*运行结果
Emp{id=3, name='amy', age=23, lists=[code, basketball], sex='F', deptId=1}
Emp{id=5, name='paul', age=34, lists=[], sex='F', deptId=2}
*/
8.$lookup
8.1关联查询
$lookup 类似与大多数关系型数据库中的 left outer 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:指定用于存放匹配文档的
新数组字段的名称
。如果指定的字段已存在,则进行覆盖。
//用dept表的主键_id 连接emp表的外键dept_id
db.dept.aggregate([
{
$lookup:
{
from: "emp",
localField: "_id",
foreignField: "dept_id",
as: "arr"
}
}
])
Java代码
@Test
void agg() {
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.lookup("emp", "_id", "dept_id", "arr")
);
AggregationResults<Map> emp = mongoTemplate.aggregate(aggregation, "dept", Map.class);
List<Map> mappedResults = emp.getMappedResults();
for (Map mappedResult : mappedResults) {
System.out.println(mappedResult);
}
}
/*
{_id=1.0, d_name=研发部, arr=[{_id=1.0, name=tom, age=13.0, likes=[basketball, football], sex=F, dept_id=1.0}, {_id=3.0, name=amy, age=23.0, likes=[code, basketball], sex=F, dept_id=1.0}]}
{_id=2.0, d_name=运维部, arr=[{_id=2.0, name=jack, age=14.0, likes=[games, douyin], sex=M, dept_id=2.0}, {_id=4.0, name=musk, age=42.0, likes=[car, space], sex=M, dept_id=2.0}, {_id=5.0, name=paul, age=34.0, likes=[], sex=F, dept_id=2.0}]}
*/