让我们来考虑一下以下文档:

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",并且Cities数组包含"from"="paris",则输出将为:

country{id="1", name="France", cities={from="Paris"}}
country{id="2", name="UK", cities={from="Paris"}}

如果用户搜索"from"="Los Angeles",而Cities数组不包含"from"="Los Angeles",那么我们必须在Cities数组中搜索包含"Detail"="new_City"的对象,因此输出将为:

country{id="1", name="France", cities={detail="new_city"}}
country{id="2", name="UK", cities={detail="new_city"}}

我如何使用聚合来执行此操作?

SpringBoot版本:SpringBoot-starter-data-MongoDB 2.7.3

谢谢

推荐答案

我已经在https://github.com/VonC/countrysearch岁时建立了一个项目

它包括src/main/java/com/example/countrysearch/service/CountryService.java,使用Aggregation

具有以下步骤的管道:

  1. Filtered Cities Addition:这将根据城市的from字段是否与searchTerm字段匹配来过滤城市.然后,过滤后的数组存储在一个名为filteredCities的新字段中.

  2. Conditional Logic for Final Cities:管道判断filteredCities是否为空(大小为零).如果为空,则管道根据设置为new_citydetail字段过滤城市时会发生回退.结果数组存储在另一个名为finalCities的新字段中.

  3. Output Formatting:管道中的最终操作投影(或 Select )最终输出中所需的字段,如idnamefinalCities.finalCities在最终输出中被重命名为cities.

   +----------------------+
   | Input Document       |
   +----------------------+
             |
             |
             v
 +------------------------+
 | Add 'filteredCities'    |  --->  Filter cities based on the 'from' field
 +------------------------+
             |
             |
             v
 +------------------------+
 | Conditional Logic      |  --->  Check if 'filteredCities' is empty; fallback to 'detail="new_city"'
 +------------------------+
             |
             |
             v
 +------------------------+
 | Output Formatting      |  --->  Project required fields ('id', 'name', 'finalCities')
 +------------------------+
             |
             |
             v
 +------------------------+
 | Final Output           |
 +------------------------+

Full CountryService.java Code

package com.example.countrysearch.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.*;
import org.springframework.stereotype.Service;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.project;

import com.example.countrysearch.model.Country;

import java.util.List;

@Service
public class CountryService {

    @Autowired
    private MongoTemplate mongoTemplate;

    public List<Country> findCountriesByCity(String searchTerm) {

        // AggregationExpression for 'filteredCities'
        AggregationExpression filterCitiesExpression = ArrayOperators.Filter.filter("cities")
                .as("city")
                .by(ComparisonOperators.Eq.valueOf("city.from").equalToValue(searchTerm));

        // First addFields operation to add 'filteredCities'
        AggregationOperation addFieldsFilteredCities = project("id", "name", "cities")
                .and(filterCitiesExpression)
                .as("filteredCities");

        // AggregationExpression for conditional logic for 'finalCities'
        AggregationExpression conditionalFinalCities = ConditionalOperators.when(
                ComparisonOperators.Eq.valueOf(
                        ArrayOperators.Size.lengthOfArray("filteredCities")
                ).equalToValue(0)
        )
        .then(
            ArrayOperators.Filter.filter("cities")
            .as("city")
            .by(
                ComparisonOperators.Eq.valueOf("city.detail").equalToValue("new_city")
            )
        )
        .otherwise("$filteredCities");

        // Second addFields operation to add 'finalCities'
        AggregationOperation addFieldsFinalCities = project("id", "name")
                .and(conditionalFinalCities).as("finalCities");

        // Final project operation to shape the output
        AggregationOperation projectFinal = project("id", "name")
                .and("finalCities").as("cities");

        // Build the aggregation pipeline
        Aggregation aggregation = Aggregation.newAggregation(
                addFieldsFilteredCities,
                addFieldsFinalCities,
                projectFinal
        );

        // Execute the aggregation
        AggregationResults<Country> result = mongoTemplate.aggregate(aggregation, "country", Country.class);

        return result.getMappedResults();
    }
}

filterCitiesExpression中,如果我想添加另一个比较,例如:ComparisonOperators.Eq... AND ComparisonOperators.Ne...;,.by()方法中的语法是什么?

要在.by()方法中添加多个比较,可以使用BooleanOperators.And.and组合多个条件.语法应如下所示:

AggregationExpression filterCitiesExpression = ArrayOperators.Filter.filter("cities")
    .as("city")
    .by(
        BooleanOperators.And.and(
            ComparisonOperators.Eq.valueOf("city.from").equalToValue(searchTerm),
            ComparisonOperators.Ne.valueOf("city.someOtherField").notEqualToValue(someOtherValue)
        )
    );

此示例将过滤包含对象的数组,其中city.from等于searchTerm,city.someOtherField不等于someOtherValue.


在需要向Cities数组添加筛选器的情况下,例如:我希望优先使用找到的对象填充城市:

  • A.city.from="Paris" and city.population>1M a.
  • B.否则,只有对象city.from="Paris"
  • C.最后,如果以上都不是,"city.detail"="new_city"

目前实施的是条件b和条件c.

  • A优先于B.
  • B优先于c.

避免:如果在cities数组中,一个文档包含"city.population" < 1000000,而另一个文档包含"city.from" = "Paris"而不包含"city.population"的信息,则新形成的数组将包含条件a)和b),而此时应该只存在条件b).

要优先使用指定的条件填充Cities数组,可以实现一系列条件($cond)语句链.这将需要更新conditionalFinalCities变量.

要实现上述行为(a>;b>;c),必须重构管道以顺序判断每个条件,只有在前一个条件没有产生任何结果时才应用下一个条件.

由于条件"a"和条件"b"的过滤逻辑不是互斥的(即,如果cities数组中的一个文档匹配条件"b"("city.from" = "Paris")但缺少"city.population"字段,而另一个文档满足条件"a"("city.from" = "Paris" and "city.population" > 1000000),则两者都将被包括在最终数组中),这将违反仅具有最高优先级的条件应占优的约束.

解决这个问题的一种方法是在聚合管道中使用其他阶段来隔离严格满足条件"a"的文档,而忽略那些满足条件"b"但不满足"a"的文档.

package com.example.countrysearch.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.*;
import org.springframework.stereotype.Service;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.project;

import com.example.countrysearch.model.Country;

import java.util.List;

@Service
public class CountryService {

    @Autowired
    private MongoTemplate mongoTemplate;

    public List<Country> findCountriesByCity(String searchTerm) {

        // Filtering for condition "a"
        AggregationOperation filterForA = project("id", "name", "cities")
                .and(ArrayOperators.Filter.filter("cities")
                        .as("city")
                        .by(BooleanOperators.And.and(
                            ComparisonOperators.Eq.valueOf("city.from").equalToValue(searchTerm),
                            ComparisonOperators.Gt.valueOf("city.population").greaterThanValue(1000000)
                        )))
                .as("filteredCitiesForA");

        // Conditional logic to isolate condition "a"
        AggregationExpression conditionalForA = ConditionalOperators.when(
                ComparisonOperators.Eq.valueOf(ArrayOperators.Size.lengthOfArray("filteredCitiesForA")).equalToValue(0)
        )
        .then(
            ConditionalOperators.when(
                ComparisonOperators.Eq.valueOf(ArrayOperators.Size.lengthOfArray(
                        ArrayOperators.Filter.filter("cities")
                            .as("city")
                            .by(ComparisonOperators.Eq.valueOf("city.from").equalToValue(searchTerm))
                )).equalToValue(0)
            )
            .then(
                ArrayOperators.Filter.filter("cities")
                    .as("city")
                    .by(ComparisonOperators.Eq.valueOf("city.detail").equalToValue("new_city"))
            )
            .otherwise(
                ArrayOperators.Filter.filter("cities")
                    .as("city")
                    .by(ComparisonOperators.Eq.valueOf("city.from").equalToValue(searchTerm))
            )
        )
        .otherwise("$filteredCitiesForA");

        // Project 'finalCities' 
        AggregationOperation projectFinal = project("id", "name")
                .and(conditionalForA).as("finalCities");

        // Build the aggregation pipeline
        Aggregation aggregation = Aggregation.newAggregation(
                filterForA,
                projectFinal
        );

        // Execute the aggregation
        AggregationResults<Country> result = mongoTemplate.aggregate(aggregation, "country", Country.class);

        return result.getMappedResults();
    }
}

其使用附加级filterForA来预过滤条件"a",并将结果存储在filteredCitiesForA中.随后的条件逻辑(conditionalForA)判断filteredCitiesForA是否为空.如果为空,则继续判断条件"b"和"c".否则,它使用条件"a"的过滤结果.这确保了最终数组(finalCities)包含满足最高优先级条件的文档.

Remember to include this new conditionalForA expression in your pipeline to replace the old one.
I have done so in CountryService.java, which compiles just fine.

Mongodb相关问答推荐

如何在Mongo Aggregate Query中创建集合的对象ID数组,并在列表中查找另一个集合中的ID

MongoDB$unionWith,如何 Select 特定文档

从 MongoDB 中的聚合结果中获取不同的值

查询有关 Go 项目中对象数组的 MongoDb 集合

如何使用 Golang 库获取 MongoDB 版本?

类型错误:db.close is not a function

MongoDB PHP UTF-8 问题

如何构建我只需要打开一次 mongodb 连接的快速应用程序?

从 MongoDB find() 结果集中识别最后一个文档

使用 ObjectId.GenerateNewId() 还是离开 MongoDB 创建一个?

在 Nodejs 中找不到模块

将数据插入 MongoDB - 没有错误,没有插入

mongodb无法启动

MongoDB 查找精确的数组匹配,但顺序无关紧要

如何确保基于特定字段的数组中的唯一项 - mongoDB?

如何在 Rails 中混合使用 mongodb 和传统数据库?

mongodb类型更改为数组

如何在第一个文档中恢复 MongoDB ChangeStream 而不仅仅是在我开始收听后更改

如何在 golang 和 mongodb 中通过 id 查找

MongoError:Can't extract geo keys from object