让我们考虑以下文件:
country{id="1", name="France", cities=[{detail="new_city"}, {from="Paris"}, {from="Lyon"} ]}
country{id="2", name="UK", cities=[{detail="new_city"}, {from="Paris"}, {from="London"}, {from="Liverpool"} ]}
如果用户搜索“from”=“Paris”并且城市数组包含“from”=“Paris”,则输出将为:
country{id="1", name="France", cities={from="Paris"}}
country{id="2", name="UK", cities={from="Paris"}}
如果用户搜索“from”=“Los Angeles”并且城市数组不包含“from”=“Los Angeles”,那么我们必须在城市数组中搜索包含“detail”=“new_city”的对象,所以输出将是:
country{id="1", name="France", cities={detail="new_city"}}
country{id="2", name="UK", cities={detail="new_city"}}
如何使用聚合来执行此操作?
SpringBoot版本:spring-boot-starter-data-mongodb 2.7.3
谢谢你
$addFields
(此处提到)和 $filter
聚合运算符的组合。
使用
$addFields
函数进行 $filter
操作用于在每个文档中创建一个名为 filteredCities
的新字段。"from"
等于搜索词 (searchTerm
) 的条件相匹配的城市数组。
addField("filteredCities").withValue(
filter("cities")
.as("city")
.by(valueOf("city.from").equalToValue(searchTerm))
)
$filter
循环遍历 cities
数组,并且对于每个对象(别名为 city
),检查 from
字段是否与 searchTerm
匹配。如果是,则该对象将包含在新的 filteredCities
数组中。
使用另一个
$addFields
操作向每个文档添加另一个名为 finalCities
的字段。这是根据您提供的标准保存要显示的城市的最终列表。
addField("finalCities").withValue(
conditional(
valueOf("filteredCities").isEmpty(),
arrayOf(valueOf("cities").filter()
.as("city")
.by(valueOf("city.detail").equalToValue("new_city"))),
valueOf("filteredCities")
)
)
$cond
条件运算符检查filteredCities
是否为空。finalCities
的值:
如果
filteredCities
为空,则使用 $filter
函数创建一个数组,仅包含原始 "detail" = "new_city"
数组中具有 cities
的城市对象。
如果
filteredCities
不为空,则将finalCities
的值设置为filteredCities
,其中已经包含满足"from" = searchTerm
条件的城市。
最后一步,
$project
,重塑每个文档以仅包含字段 id
、name
和 finalCities
(重命名为 cities
)。
Input Document
---------------
{
id: "1",
name: "France",
cities: [{detail: "new_city"}, {from: "Paris"}, {from: "Lyon"}]
}
|
|
v
$addFields (filteredCities)
---------------------------
{
id: "1",
name: "France",
cities: [{detail: "new_city"}, {from: "Paris"}, {from: "Lyon"}],
filteredCities: [{from: "Paris"}] <--- New field based on $filter
}
|
|
v
$addFields (finalCities)
------------------------
{
id: "1",
name: "France",
cities: [{detail: "new_city"}, {from: "Paris"}, {from: "Lyon"}],
filteredCities: [{from: "Paris"}],
finalCities: [{from: "Paris"}] <--- New field based on $cond
}
|
|
v
$project
--------
{
id: "1",
name: "France",
cities: [{from: "Paris"}] <--- Reshaped document
}
假设您有一个带有
Country
字段的 List<Map<String, String>> cities
类,则用于执行此聚合管道的 Java 代码可能如下所示:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
import java.util.List;
import java.util.Map;
public class CountryService {
@Autowired
private MongoTemplate mongoTemplate;
public List<Map> getCountriesBasedOnCities(String searchTerm) {
Aggregation aggregation = newAggregation(
addFields()
.addField("filteredCities").withValue(
filter("cities")
.as("city")
.by(valueOf("city.from").equalToValue(searchTerm))
).build(),
addFields()
.addField("finalCities").withValue(
conditional(
valueOf("filteredCities").isEmpty(),
arrayOf(valueOf("cities").filter()
.as("city")
.by(valueOf("city.detail").equalToValue("new_city"))),
valueOf("filteredCities")
)
).build(),
project("id", "name")
.and("finalCities").as("cities")
);
AggregationResults<Map> results = mongoTemplate.aggregate(aggregation, "country", Map.class);
return results.getMappedResults();
}
}
"country"
代表 MongoDB 集合:将其名称替换为存储 country
文档的集合中的名称。