Operação de consulta de pipeline de agregação MongoDB $facet$lookup$unwind$group no SpringBoot

Prefácio, documentação oficial, conceitos no MongoTemplate

Prefácio

2023/3/14 Eu sou o autor. O artigo original foi publicado pela primeira vez no blog park (2022/12/09). Vou copiá-lo para CSDN hoje. Sim, eu sou o autor.

Recentemente, tenho trabalhado na operação do pipeline de agregação do MongoDB com base no SpringBoot. As instruções JSON não são difíceis de escrever, mas é difícil esclarecer a lógica e a ordem. Além disso, ao operar o pipeline de agregação em Java (Springboot), o uso de alguns operadores não é claro. Além disso, você pode consultá-los on-line. Os exemplos estão dispersos e muitos não são intuitivos e abrangentes.

Então, depois de ler os documentos oficiais e alguns artigos técnicos compartilhados por indivíduos, fiz meus próprios testes e verificações, compilei esta nota e compartilhei a operação do pipeline de agregação do MongoDB baseado no SpringBoot.

Ele se concentra principalmente na compreensão das duas operações fornecidas pelo MongoDB Template para implementar pipelines de agregação. O foco é baseado em $group, $lookup, $unwind, $facet, esses operadores. O código real também envolve $match, $count, $ Use de sortByCount.

Ao mesmo tempo, vamos analisar várias definições no modelo MongoDB (consulte "Conceitos no MongoTemplate"), espero que seja útil para todos.

Por favor, siga a navegação do título para comer, o sabor será melhor ~

Pode ser reproduzido, mas indique a fonte.

2023/3/7: Além disso, uma explicação detalhada das operações de consulta e atualização de um campo de array foi adicionada recentemente. Se necessário, você pode clicar aqui para pular.

Explicação detalhada da consulta de array MongoDB, operações de atualização e $elemMatch, arrayFilter, placeholder$, $push e outros operadores - CSDN Blog

Documentação oficial

$facet (agregação) — Manual do MongoDB

Conceitos relacionados ao MongoTemplate

  • MongoTemplate : objeto fornecido oficialmente para operar o MongoDB. Localizado em:org.springframework.data.mongodb.core. Quando usado, a injeção é necessária.

  • Consulta : Objeto usado para criar condições de consulta. Localizado em: pacote org.springframework.data.mongodb.core.query. Ao usá-lo, geralmente você precisa passar condições de consulta como "Critérios".

  • Critérios: Um objeto para construção de condições de consulta específicas, localizado no mesmo pacote que Query.

  

  • AggregationOperation: O objeto de operação do pipeline de agregação. Esta é uma operação adequada para estágios de pipeline agregado, como $group/$lookup/$unwind/$sort.... Ao usá-lo, você precisa primeiro construir a operação de agregação correspondente , como $group (requer operações de construção específicas), você pode criar vários e, finalmente, passá-los para o objeto Aggregation e, em seguida, passá-los para o modelo para realizar a agregação do pipeline. deitar em:

  • Agregação: A coleção de estágios do Pipeline, que é a coleção de AggregationOperation acima, armazena todas as operações de agregação acima juntas.Quando o modelo chama o método agregado, este objeto é passado.

  • As classes acima estão localizadas no pacote org.springframework.data.mongodb.core.agregation;

  • Agregados: objeto de operação do estágio de pipeline. Tem quase as mesmas funções da agregação, mas é mais flexível. Geralmente, além dos operadores pré-fornecidos, você também pode passar o objeto de operação Bson para implementação flexível. A dificuldade geral de uso pode ser maior que a agregação.

  • Bson, BsonDocument, BsonField: Entendo Bson como uma expressão flexível, definições de construção, como condições de consulta e operadores de agregação, podem ser recebidas por ele e finalmente passadas para o método agregado do modelo para realizar operações de agregação. BsonDocument é uma implementação específica de Bson e é usado para construir objetos de expressão de maneira flexível. Em relação a esta parte, você pode ler abaixo para obter detalhes. BsonField também é uma classe para construir expressões de agregação flexíveis, como definir rapidamente {"count": { $sum: 1 } e passá-lo para o estágio de agregação específico como parte da operação de agregação.

  • As classes acima estão localizadas no pacote com.mongodb.client.model; Bson/BsonDocument está em outro pacote. org.bson. Se você estiver interessado, você mesmo pode encontrá-lo no código-fonte.

Ambiente de desenvolvimento e documentação de referência

JDK1.8 +Maven

SpringBoot (Springboot-starter-pai): 2.7.5

Mongodb (spring-boot-starter-data-mongodb) 4.6.1

Documentação de referência:

SpringBoot integra Spring Data Mongodb e opera MongoDB em detalhes | Xiaodudin Technology Stack

Operação de consulta Spring Boot MongoDB (BasicQuery, BSON) | Fórum de banco de dados

Consulte os casos compartilhados pelos internautas acima.

Código e exemplos

1. Operadores $count e $match

definição oficial

$count: Passa um documento para o próximo estágio que contém uma contagem do número de documentos inseridos no estágio.

Explicação detalhada da consulta de array MongoDB, operações de atualização e $elemMatch, arrayFilter, placeholder$, $push e outros operadores - CSDN Blog

É para contar a quantidade de documentos que existem na fase atual (a fase de operação do pipeline de agregação).

$match: filtra os documentos para passar apenas os documentos que correspondem às condições especificadas para o próximo estágio do pipeline.

Filtre os dados que atendem às condições para o próximo estágio do pipeline.

gramática


{ $count: <string> // 这里的名称随便写,最后显示出来的结果就是 xxx : 总数 } 

// match语法
{ $match: { <query> } }  // 就是传入查询语句Json格式

Exemplos oficiais de operações no MongoDB


// 数据
{ "_id" : 1, "subject" : "History", "score" : 88 }
{ "_id" : 2, "subject" : "History", "score" : 92 }
{ "_id" : 3, "subject" : "History", "score" : 97 }
{ "_id" : 4, "subject" : "History", "score" : 71 }
{ "_id" : 5, "subject" : "History", "score" : 79 }
{ "_id" : 6, "subject" : "History", "score" : 83 }

// 执行

db.scores.aggregate(
// 先用match查找匹配的文档,然后直接用count统计当前match阶段存在的文档数量。
[{
     
     $match: {
     
     score: {
     
     $gt: 80}}},
{
     
     $count: "passing_scores" // 这里的passing_scores 也可以是其他任意名称
}]
)

// 返回结果
{ "passing_scores" : 4 }

Código Java implementado em MongoTemplate

Para dados, consulte o exemplo oficial acima. A seguir estão as implementações de agregação e agregações, respectivamente. Qualquer um está bom.


    /**
     * @Author zaoyu
     */
    @Autowired
    private MongoTemplate mongoTemplate;

    private String DEMO_COLLECTION = "demo";


    /**
     * 用Aggregates和Bson构建聚合操作对象,用预先生成的MongoCollection对象调用aggregate执行即可。
     */
    @Test
    public void testCountWithAggregates(){
        MongoCollection<Document> collection = mongoTemplate.getCollection(DEMO_COLLECTION);
        // Aggregates提供各种操作符,返回一个Bson对象。这里用match,然后用Filters来实现过滤条件的构建,也是返回一个Bson对象。
        Bson matchBson = Aggregates.match(Filters.gt("score", 80));
        // 直接用Aggregates的count方法,如果不传自定义的名称,默认用“count”接收。
        Bson countBson = Aggregates.count("myCount");
        // 构建一个List<Bson>, 并把每一个聚合操作Bson加进去,最后传入aggregate方法中执行。
        List<Bson> bsonList = new ArrayList<>();
        bsonList.add(matchBson);
        bsonList.add(countBson);
        AggregateIterable<Document> resultList = collection.aggregate(bsonList);
        for (Document document : resultList) {
            System.out.println("result is :" + document);
        }
    }

    /**
     * @Author zaoyu
     * 用Aggregation集合接收聚合操作,用MongoTemplate对象直接调用aggregate,传入聚合操作集合、表名、映射对象。
     */
    @Test
    public void testCountWithAggregation(){
        // 构建查询match条件:分数大于80
        MatchOperation matchOperation = Aggregation.match(Criteria.where("score").gt(80));
        // 构建count操作,用“myCount”名称接收
        CountOperation countOperation = Aggregation.count().as("myCount");
        // 传入多个aggregation(聚合操作),用Aggregation对象接收。
        Aggregation aggregation = Aggregation.newAggregation(matchOperation, countOperation);
        // 直接用mongoTemplate调用aggregate方法,传入aggregation集合,表名,还有用什么对象接收数据,这里我用Document接收,不再建类。
        AggregationResults<Document> resultList = mongoTemplate.aggregate(aggregation, DEMO_COLLECTION, Document.class);
        for (Document document : resultList) {
            System.out.println("result is :" + document);
        }
    }
// 以上2个方法的输出结果一样,如下。
result is :Document{
     
     {myCount=4}}

2. Operador de grupo $

definição oficial

A fase $group separa os documentos em grupos de acordo com uma "chave de grupo". A saída é um documento para cada chave de grupo exclusiva. Uma chave de grupo geralmente é um campo ou grupo de campos. A chave do grupo também pode ser o resultado de uma expressão. Use o campo _id no estágio de pipeline $group para definir a chave do grupo.

Provavelmente significa agrupar documentos. O formato da saída é que um dado tenha uma chave de agrupamento exclusiva. Isso pode ser simplesmente comparado ao grupo por grupo do MySQL.

gramática


{
  $group:
    {
      _id: <expression>, // 用来分组的字段
      <field1>: { <accumulator1> : <expression1> }, // 对某字段做处理 accumulator操作。 
      ...
    }
 }

Exemplos oficiais de operações no MongoDB


// 插入数据
db.sales.insertMany([
  { "_id" : 1, "item" : "abc", "price" : NumberDecimal("10"), "quantity" : NumberInt("2"), "date" : ISODate("2014-03-01T08:00:00Z") },
  { "_id" : 2, "item" : "jkl", "price" : NumberDecimal("20"), "quantity" : NumberInt("1"), "date" : ISODate("2014-03-01T09:00:00Z") },
  { "_id" : 3, "item" : "xyz", "price" : NumberDecimal("5"), "quantity" : NumberInt( "10"), "date" : ISODate("2014-03-15T09:00:00Z") },
  { "_id" : 4, "item" : "xyz", "price" : NumberDecimal("5"), "quantity" :  NumberInt("20") , "date" : ISODate("2014-04-04T11:21:39.736Z") },
  { "_id" : 5, "item" : "abc", "price" : NumberDecimal("10"), "quantity" : NumberInt("10") , "date" : ISODate("2014-04-04T21:23:13.331Z") },
  { "_id" : 6, "item" : "def", "price" : NumberDecimal("7.5"), "quantity": NumberInt("5" ) , "date" : ISODate("2015-06-04T05:08:13Z") },
  { "_id" : 7, "item" : "def", "price" : NumberDecimal("7.5"), "quantity": NumberInt("10") , "date" : ISODate("2015-09-10T08:43:00Z") },
  { "_id" : 8, "item" : "abc", "price" : NumberDecimal("10"), "quantity" : NumberInt("5" ) , "date" : ISODate("2016-02-06T20:20:13Z") },
])

// Executa o grupo, aqui também adiciona match, project e sort. Use a exibição em conjunto.


db.getCollection("sales").aggregate(
  // 第一个聚合管道:过滤出日期在2014-01-01到2015-01-01之间的数据
[ {
    $match : { "date": { $gte: new ISODate("2014-01-01"), $lt: new ISODate("2015-01-01") } }
  },
 // 第二个聚合管道:处理一下日期格式,方便等下做group。  
 {
     
     $project: 
     {
     
     quantity:1, 
         price:1,
         myDate:{
     
     "$dateToString":{
     
     format: "%Y-%m-%d", date: "$date"}}
 }},
 // 第三个聚合管道:分组统计,先按照日期分组,再统计每天的销售数量。        
 {
     
     $group:{
     
     _id:"$myDate", 
    perDayQuantity:{
     
     $sum:"$quantity"},
    myCount: {
     
     $sum:1}
   }},
 // 第四个聚合管道:按照每日销售数量降序排序
 {
     
     $sort:{
     
     "perDayQuantity":-1}}
])

Código Java implementado em MongoTemplate


  /**
   * @Author zaoyu
     * Aggregation 实现match, group, sort。
     */
    @Test
    public void testGroupAggregations(){
        // 第一阶段,过滤查询日期介于14-1-1~15-1-1之间的数据,用Aggregation实现类MatchOperation接收。
        MatchOperation match = Aggregation.match(Criteria
                .where("date").gte(Instant.parse("2014-01-01T08:00:00.000Z"))
                .andOperator(Criteria.where("date").lte(Instant.parse("2015-01-01T08:00:00.000Z"))));
        // 第二阶段,处理一下日期格式,方便等下做group,用ProjectionOperation接收,也是Aggregation的实现类。
        ProjectionOperation project = Aggregation.project("quantity", "price")
                .andExpression("{\"$dateToString\":{format: \"%Y-%m-%d\", date: \"$date\"}}").as("myDate");
        // 第三阶段,分组统计,先按照日期分组,再统计每天的销售数量。
        GroupOperation group = Aggregation.group("myDate")
                .sum("quantity").as("perDayQuantity")
                // 这里是计算文档条数
                .count().as("myCount");
        // 第四阶段,排序。按照perDayQuantity字段升序展示。
        SortOperation sort = Aggregation.sort(Sort.Direction.ASC, "perDayQuantity");
        // 用newAggregation接收以上多个阶段的管道聚合指令,执行,得到结果。
        Aggregation aggregations =Aggregation.newAggregation(match, project, group, sort);
        AggregationResults<Document> resultList = mongoTemplate.aggregate(aggregations, SALES_COLLECTION, Document.class);
        for (Document document : resultList) {
            System.out.println("result is :" + document);
        }
    }
// 返回结果
result is :Document{
     
     {_id=2014-03-01, perDayQuantity=3, myCount=2}}
result is :Document{
     
     {_id=2014-03-15, perDayQuantity=10, myCount=1}}
result is :Document{
     
     {_id=2014-04-04, perDayQuantity=30, myCount=2}}

3. Operador $ desenrolar

definição oficial

Desconstrói um campo de matriz dos documentos de entrada para gerar um documento para cada elemento. Cada documento de saída é o documento de entrada com o valor do campo da matriz substituído pelo elemento.

O significado geral é dividir os campos da matriz do documento de entrada em elementos, um por um, e combiná-los com os dados originais para formar uma nova saída do documento. Por exemplo, se houver 10 dados no original, cada dado tem uma matriz com comprimento 3, então se você dividi-lo (se os elementos não forem repetidos), você obterá 30 dados.

gramática


{ $unwind: <field path> }  

和

{
  $unwind:
    {
      path: <field path>,  // path是固定名称,沿用即可。 <field path> 是数组字段,就是你要拆分的字段(值得是一个数组,不然没有意义)
      includeArrayIndex: <string>,
      preserveNullAndEmptyArrays: <boolean>
    }
}

Exemplos oficiais de operações no MongoDB


// 插入数据
db.inventory.insertOne({ "_id" : 1, "item" : "ABC1", sizes: [ "S", "M", "L"] })

// 执行unwind操作,这里是把 sizes字段的数组拆分出来 
db.inventory.aggregate( [ { $unwind : "$sizes" } ] )

// 执行结果  可以看到每条数据的sizes不再是list,而是具体的元素。
{ "_id" : 1, "item" : "ABC1", "sizes" : "S" }
{ "_id" : 1, "item" : "ABC1", "sizes" : "M" }
{ "_id" : 1, "item" : "ABC1", "sizes" : "L" }

Observe que se o campo a ser dividido for uma matriz vazia ou nula, os dados de saída reais não conterão esse dado. Exemplo abaixo.


// 插入多条数据,这里还放了三条特殊数据,一个是空集合,一个是null,一个是没有要拆分的字段 sizes
db.clothing.insertMany([
  { "_id" : 1, "item" : "Shirt", "sizes": [ "S", "M", "L"] },
  { "_id" : 2, "item" : "Shorts", "sizes" : [ ] },
  { "_id" : 3, "item" : "Hat", "sizes": "M" },
  { "_id" : 4, "item" : "Gloves" },
  { "_id" : 5, "item" : "Scarf", "sizes" : null }
])

// 执行$unwind
db.clothing.aggregate( [ { $unwind: { path: "$sizes" } } ] )

// 返回结果, 可以看到,sizes值为空数组和null的id=2以及id=5的数据都没有展示出来,同时没有该字段的id=4,也没有展示出来。
{ _id: 1, item: 'Shirt', sizes: 'S' },
{ _id: 1, item: 'Shirt', sizes: 'M' },
{ _id: 1, item: 'Shirt', sizes: 'L' },

Código Java implementado em MongoTemplate

A seguir estão as implementações de agregação e agregações, respectivamente.


  /**
     * @Author zaoyu
     * Aggregation 实现$unwind
     */
    @Test
    public void testUnwindAggregations() {
        String CLOTHING_COLLECTION = "clothing";
        // 调用Aggregation中的unwind的聚合操作符
        UnwindOperation unwind = Aggregation.unwind("sizes");
        // 用newAggregation接收管道聚合指令,执行,得到结果。
        Aggregation aggregations =Aggregation.newAggregation(unwind);
        // mongoTemplate 直接调用aggregate方法,传入Aggregation对象,基于的表,映射类(这里简单化,我用Document)
        AggregationResults<Document> resultList = mongoTemplate.aggregate(aggregations, CLOTHING_COLLECTION, Document.class);
        for (Document document : resultList) {
            System.out.println("result is :" + document);
        }
    }
    /**
     * @Author zaoyu
     * Aggregates/Bson 实现$unwind
     */
    @Test
    public void testUnwindAggregates(){
        String CLOTHING_COLLECTION = "clothing";
        // 调用Aggregates的unwind聚合操作符 注意,Aggregates这里需要传入$
        Bson unwindBson = Aggregates.unwind("$sizes");
        // 建一个List<Bson> 把unwindBson传进去
        List<Bson> bsonList = new ArrayList<>();
        bsonList.add(unwindBson);
        // mongoTemplate先获得对应的collection对象,然后调用aggregate,传入List<Bson> 获得结果
        MongoCollection<Document> collection = mongoTemplate.getCollection(CLOTHING_COLLECTION);
        AggregateIterable<Document> resultList = collection.aggregate(bsonList);
        for (Document document : resultList) {
            System.out.println("result is :" + document);
        }
    }

4. Operador $lookup

definição oficial

Executa uma junção externa esquerda em uma coleção no mesmo banco de dados para filtrar documentos da coleção "juntada" para processamento. O estágio $lookup adiciona um novo campo de array a cada documento de entrada. O novo campo de matriz contém os documentos correspondentes da coleção "juntada".

O estágio $lookup passa esses documentos remodelados para o próximo estágio.

A partir do MongoDB 5.1, $lookup funciona em coleções fragmentadas.

Na verdade, pode ser facilmente entendido por analogia com a subconsulta do Mysql. Os dados correspondentes de outra tabela serão armazenados nos dados atuais como um array, e um campo precisa ser customizado para receber a exibição.

Por exemplo, a seguinte instrução SQL


SELECT *, <output array field>
FROM collection
WHERE <output array field> IN (
   SELECT *
   FROM <collection to join>
   WHERE <foreignField> = <collection.localField>
);

[Nota especial] Se o banco de dados atual for implantado em um cluster, $lookup não terá efeito se a versão do banco de dados for anterior a 5.1. Caso seu banco de dados esteja clusterizado e você queira utilizar $lookup, não deixe de verificar se a versão é maior ou igual a 5.1, caso contrário ela não será encontrada. Eu não sabia disso há algum tempo e nunca tive ideia de por que os dados não puderam ser encontrados.

gramática


{
   $lookup:
     {
       from: <collection to join>,  // 要联表查的表名
       localField: <field from the input documents>, // 当前表的要和联表关联的字段
       foreignField: <field from the documents of the "from" collection>, // 要被关联表的外键字段 
       as: <output array field> // 定义一个字段接收匹配关联的数据
     }
}

Exemplos oficiais de operações no MongoDB


// 插入表orders数据  
db.orders.insertMany( [
   { "_id" : 1, "item" : "almonds", "price" : 12, "quantity" : 2 },
   { "_id" : 2, "item" : "pecans", "price" : 20, "quantity" : 1 },
   { "_id" : 3  }
] )

// 插入表inventory数据
db.inventory.insertMany( [
   { "_id" : 1, "sku" : "almonds", "description": "product 1", "instock" : 120 },
   { "_id" : 2, "sku" : "bread", "description": "product 2", "instock" : 80 },
   { "_id" : 3, "sku" : "cashews", "description": "product 3", "instock" : 60 },
   { "_id" : 4, "sku" : "pecans", "description": "product 4", "instock" : 70 },
   { "_id" : 5, "sku": null, "description": "Incomplete" },
   { "_id" : 6 }
] )

Executar código


db.orders.aggregate( [ // db.orders 表示基于orders做聚合操作
   {
     $lookup:
       {
         from: "inventory",   // 联表inventory 
         localField: "item",  // 当前orders的字段
         foreignField: "sku", // inventory中的sku字段,和orders的item关联 
         as: "inventory_docs" // 定义一个字段名接收 inventory中sku 和orders的item相同的数据,数组形式。 
       }
  }
] )

Retornar resultados


{
   "_id" : 1,
   "item" : "almonds",
   "price" : 12,
   "quantity" : 2,
   "inventory_docs" : [  // 这个inventory_docs字段就是自己命名的字段,存储着来自inventory的数据
      { "_id" : 1, "sku" : "almonds", "description" : "product 1", "instock" : 120 }
   ]
}
{
   "_id" : 2,
   "item" : "pecans",
   "price" : 20,
   "quantity" : 1,
   "inventory_docs" : [
      { "_id" : 4, "sku" : "pecans", "description" : "product 4", "instock" : 70 }
   ]
}
{
   "_id" : 3,
   "inventory_docs" : [
      { "_id" : 5, "sku" : null, "description" : "Incomplete" },
      { "_id" : 6 }
   ]
}

Código Java implementado em MongoTemplate

A seguir estão as implementações de agregação e agregações, respectivamente.


 /**
     * @Author zaoyu
     * Aggregation 实现$lookup
     */
    @Test
    public void testLookupAggregations(){
        String INVENTORY_COLLECTION = "inventory";
        String ORDERS_COLLECTION = "orders";
        // Aggregation类,直接可以调用lookup方法,传入要关联的表、当前表和关联表关联的字段、要关联的表的字段、自定义名称接收关联匹配的数据
        LookupOperation lookup = Aggregation.lookup(INVENTORY_COLLECTION, "item", "sku", "inventory_docs");
        // 用newAggregation接收管道聚合指令,执行,得到结果。
        Aggregation aggregations =Aggregation.newAggregation(lookup);
        // mongoTemplate 直接调用aggregate方法,传入Aggregation对象,基于的表,映射类(这里简单化,我用Document)
        AggregationResults<Document> resultList = mongoTemplate.aggregate(aggregations, ORDERS_COLLECTION, Document.class);
        for (Document document : resultList) {
            System.out.println("result is :" + document);
        }
    }

    /**
     * @Author zaoyu
     * Aggregates/Bson 实现$lookup
     */
    @Test
    public void testLookupAggregates(){
        String INVENTORY_COLLECTION = "inventory";
        String ORDERS_COLLECTION = "orders";
        // 这里用Aggregates类直接调用lookup,传入的参数和上面的Aggregations的lookup是一样的,只不过这里返回的结果是一个Bson对象。
        Bson lookupBson = Aggregates.lookup(INVENTORY_COLLECTION, "item", "sku", "inventory_docs");
        // 建一个List<Bson> 把lookupBson传进去
        List<Bson> bsonList = new ArrayList<>();
        bsonList.add(lookupBson);
        // mongoTemplate先获得对应的collection对象,然后调用aggregate,传入List<Bson> 获得结果
        MongoCollection<Document> collection = mongoTemplate.getCollection(ORDERS_COLLECTION);
        AggregateIterable<Document> resultList = collection.aggregate(bsonList);
        for (Document document : resultList) {
            System.out.println("result is :" + document);
        }
    }

Retornar resultados


result is :Document{
     
     {_id=1.0, item=almonds, price=12.0, quantity=2.0, inventory_docs=[Document{
     
     {_id=1.0, sku=almonds, description=product 1, instock=120.0}}]}}
result is :Document{
     
     {_id=2.0, item=pecans, price=20.0, quantity=1.0, inventory_docs=[Document{
     
     {_id=4.0, sku=pecans, description=product 4, instock=70.0}}]}}
result is :Document{
     
     {_id=3.0, inventory_docs=[Document{
     
     {_id=5.0, sku=null, description=Incomplete}}, Document{
     
     {_id=6.0}}]}}

5. Operador $ faceta

definição oficial

Processa vários pipelines de agregação em um único estágio no mesmo conjunto de documentos de entrada. Cada subpipeline possui seu próprio campo no documento de saída, onde seus resultados são armazenados como uma matriz de documentos.

Os documentos de entrada são passados ​​para o estágio $facet apenas uma vez. $facet permite várias agregações no mesmo conjunto de documentos de entrada, sem a necessidade de recuperar os documentos de entrada várias vezes.

Simplificando, o facet pode concluir operações de pipeline de vários estágios em operações de pipeline de facet. Reduza o número de vezes que você precisa buscar documentos de entrada.

Pessoalmente, acho que existe um cenário em que facet é muito adequado para uso: ao consultar dados de documentos em páginas, o número total que atende às condições de consulta também é consultado. Se você usar $facet, poderá obter os dados da página e o número total ao mesmo tempo, sem ter que fazer duas consultas ao banco de dados (consulta dados de paginação e número total, respectivamente).

gramática


{ $facet:
   {
      <outputField1>: [ <stage1>, <stage2>, ... ],  // 这里outputpufield 是自己定义的用来接收stage集合返回的文档数据。  
      <outputField2>: [ <stage1>, <stage2>, ... ],  // 可以基于上一个Facet继续做facet
      ...
   }
}

Exemplos oficiais de operações no MongoDB


// 数据  插入artwork 表中 
{ "_id" : 1, "title" : "The Pillars of Society", "artist" : "Grosz", "year" : 1926,
  "price" : NumberDecimal("199.99"),
  "tags" : [ "painting", "satire", "Expressionism", "caricature" ] }
{ "_id" : 2, "title" : "Melancholy III", "artist" : "Munch", "year" : 1902,
  "price" : NumberDecimal("280.00"),
  "tags" : [ "woodcut", "Expressionism" ] }
{ "_id" : 3, "title" : "Dancer", "artist" : "Miro", "year" : 1925,
  "price" : NumberDecimal("76.04"),
  "tags" : [ "oil", "Surrealism", "painting" ] }
{ "_id" : 4, "title" : "The Great Wave off Kanagawa", "artist" : "Hokusai",
  "price" : NumberDecimal("167.30"),
  "tags" : [ "woodblock", "ukiyo-e" ] }
{ "_id" : 5, "title" : "The Persistence of Memory", "artist" : "Dali", "year" : 1931,
  "price" : NumberDecimal("483.00"),
  "tags" : [ "Surrealism", "painting", "oil" ] }
{ "_id" : 6, "title" : "Composition VII", "artist" : "Kandinsky", "year" : 1913,
  "price" : NumberDecimal("385.00"),
  "tags" : [ "oil", "painting", "abstract" ] }
{ "_id" : 7, "title" : "The Scream", "artist" : "Munch", "year" : 1893,
  "tags" : [ "Expressionism", "painting", "oil" ] }
{ "_id" : 8, "title" : "Blue Flower", "artist" : "O'Keefe", "year" : 1918,
  "price" : NumberDecimal("118.42"),
  "tags" : [ "abstract", "painting" ] }


// 执行$facet聚合
db.artwork.aggregate( [
  {
    $facet: {
        // 第一个Facet操作,按照tag分类:先用unwind拆分tags字段的数组值,交给下一个聚合 $sortByCount, 按照tags的个数排序。
      "categorizedByTags": [
        { $unwind: "$tags" },
        { $sortByCount: "$tags" }
      ],
    // 第二个Facet操作,按照price分类:先过滤数据(只处理存在price数据的文档),然后执行$bucket按照价格区间分组 0~150,151~200, 201~300, 301~400这样。
      "categorizedByPrice": [
        { $match: { price: { $exists: 1 } } },
        {
          $bucket: {
            groupBy: "$price",
            boundaries: [  0, 150, 200, 300, 400 ],
            default: "Other",
            output: {
              "count": { $sum: 1 },
              "titles": { $push: "$title" }
            }
          }
        }
      ],
// 第三个Facet, 按照years分类。 分成4个区间。
      "categorizedByYears(Auto)": [
        {
          $bucketAuto: {
            groupBy: "$year",
            buckets: 4
          }
        }
      ]
    }
  }
])

Código Java implementado em MongoTemplate

Nota: Para a implementação do código a seguir, a fonte de dados refere-se aos dados do exemplo oficial acima, a tabela de arte. Por favor, insira você mesmo os dados.

1. Implementado usando objetos Aggregation


 /**
     * @Author zaoyu
     * Aggregation 实现$facet
     */
    @Test
    public void testFacetAggregations(){
        String ARTWORK_COLLECTION = "artwork";
        // Facet中第一组分类(categorizedByTags)的两个聚合操作unwind 和 sortByCount
        UnwindOperation unwindForByTags = Aggregation.unwind("$tags");
        SortByCountOperation sortByCountForByTags = Aggregation.sortByCount("$tags");

        // Facet中第二组分类(categorizedByPrice)的聚合操作match 和 match
        MatchOperation matchForByPrice = Aggregation.match(Criteria.where("price").exists(true));
        // 分别传入bucket分组的字段price,设置区间值,并设置桶内条数统计和值(这里用titles接收title的值)
        BucketOperation bucketForByPrice = Aggregation.bucket("$price")
                .withBoundaries(0, 150, 200, 300, 400)
                .withDefaultBucket("Other")
                .andOutput("count").sum(1).as("count")
                .andOutput("$title").push().as("titles");

        // Facet中第三组分类 (categorizedByYears(Auto))的聚合操作,按年自动分成4个区间。
        BucketAutoOperation bucketForByYears = Aggregation.bucketAuto("$year", 4);

        // Aggregation调用facet方法,按照组别分类顺序,把每一组的聚合操作和输出的名称传进去。
        FacetOperation facetOperation = Aggregation.facet(unwindForByTags, sortByCountForByTags).as("categorizedByTags")
                .and(matchForByPrice, bucketForByPrice).as("categorizedByPrice")
                .and(bucketForByYears).as("categorizedByYears(Auto)");
        // 把facetOperation传入newAggregation得到Aggregation对象,调用mongoTemplate的Aggregate方法执行得到结果
        Aggregation aggregation = Aggregation.newAggregation(facetOperation);
        AggregationResults<Document> resultList = mongoTemplate.aggregate(aggregation, ARTWORK_COLLECTION, Document.class);
        for (Document document : resultList) {
            System.out.println("result is :" + document);
        }
    }

2. Implementar usando agregados


/**
     * @Author zaoyu
     * Aggregates 实现$facet
     */
    @Test
    public void testFacetAggregates() {
        String ARTWORK_COLLECTION = "artwork";
        // Facet中第一组分类(categorizedByTags)的两个聚合操作unwind 和 sortByCount
        Bson unwindBsonForByTags = Aggregates.unwind("$tags");
        Bson sortByCountBsonForByTags = Aggregates.sortByCount("$tags");
        // 新建Facet对象,传入第一组分类的接收名称,以及在第一组分类中要做的聚合操作。
        Facet categorizedByTags = new Facet("categorizedByTags", unwindBsonForByTags, sortByCountBsonForByTags);

        // Facet中第二组分类(categorizedByPrice)的聚合操作match 和 match
        Bson matchBsonForPrice = Aggregates.match(Filters.exists("price"));
        // 这里面要新建BsonField构建 {"count": { $sum: 1 }  和 "titles": { $push: "$title" }} 作为第二组分类中$Bucket聚合操作中output值
        BsonField countOutput = new BsonField("count", new Document("$sum", 1));
        BsonField titleOutput = new BsonField("titles", new Document("$push", "$price"));
        // 上面2个操作传入到BucketOption对象,最后传到bucket操作
        BucketOptions bucketOptions = new BucketOptions().defaultBucket("Other").output(countOutput).output(titleOutput);
        Bson bucketBsonForByPrice = Aggregates.bucket("$price", Arrays.asList(0, 150, 200, 300, 400), bucketOptions);
        Facet categorizedByPrice = new Facet("categorizedByPrice", matchBsonForPrice, bucketBsonForByPrice);

        // Facet中第三组分类 (categorizedByYears(Auto))的聚合操作,按年自动分成4个区间。
        Bson bucketAutoBsonForByYears = Aggregates.bucketAuto("$year", 4);
        Facet categorizedByYears = new Facet("categorizedByYears", bucketAutoBsonForByYears);

        // 新建一个List<Facet>把每组分类的Facet对象传进去。
        List<Facet> facetList = new ArrayList<>();
        facetList.add(categorizedByTags);
        facetList.add(categorizedByPrice);
        facetList.add(categorizedByYears);
        // 调用Aggregates的facet方法,传入List<Facet>得到最终Bson对象,并添加到Bson集合中。
        Bson facetBson = Aggregates.facet(facetList);
        List<Bson> bsonList = new ArrayList<>();
        bsonList.add(facetBson);
        // 调用方法执行得到结果
        MongoCollection<Document> collection = mongoTemplate.getCollection(ARTWORK_COLLECTION);
        AggregateIterable<Document> resultList = collection.aggregate(bsonList);
        for (Document document : resultList) {
            System.out.println("result is :" + document);
        }
    }

O resultado final do retorno é o mesmo.


result is :Document{
     
     {categorizedByTags=[Document{
     
     {_id=painting, count=6}}, Document{
     
     {_id=oil, count=4}}, 
Document{
     
     {_id=Expressionism, count=3}}, Document{
     
     {_id=Surrealism, count=2}}, 
Document{
     
     {_id=abstract, count=2}}, Document{
     
     {_id=woodblock, count=1}}, 
Document{
     
     {_id=ukiyo-e, count=1}}, Document{
     
     {_id=satire, count=1}}, 
Document{
     
     {_id=caricature, count=1}}, Document{
     
     {_id=woodcut, count=1}}
], 
categorizedByPrice=[Document{
     
     {_id=0, titles=[76.04, 118.42]}}, 
Document{
     
     {_id=150, titles=[199.99, 167.30]}}, 
Document{
     
     {_id=200, titles=[280.00]}}, 
Document{
     
     {_id=300, titles=[385.00]}}, 
Document{
     
     {_id=Other, titles=[483.00]}}], 
categorizedByYears=[Document{
     
     {_id=Document{
     
     {min=null, max=1902.0}}, count=2}}, 
Document{
     
     {_id=Document{
     
     {min=1902.0, max=1918.0}}, count=2}}, 
Document{
     
     {_id=Document{
     
     {min=1918.0, max=1926.0}}, count=2}}, 
Document{
     
     {_id=Document{
     
     {min=1926.0, max=1931.0}}, count=2}}]
}}
// 为方便好看,稍微调了下格式。

resumo

No geral, o MonogoDB fornece oficialmente informações muito detalhadas, mas para operações no nível Java ou no nível SpringBoot, a documentação é relativamente simples.

Pessoalmente falando, o método fornecido por Aggregations é mais direto e mais adequado para alunos que não estão familiarizados com a operação do Mongo no Springboot. Os agregados serão mais flexíveis, mas você precisa saber a conversão e aquisição entre Document, BsonField e Bson.

Se houver algum erro ou omissão, corrija-me.

Espero que este artigo possa ajudá-lo. Se ajudar você, curta, comente, siga e recompense ~~

Acho que você gosta

Origin blog.csdn.net/harlan95/article/details/129521760
Recomendado
Clasificación