Agregar

Descrição

A etapa aggregate calcula resultados agregados (por exemplo, contagem, soma) dos documentos retornados pela etapa anterior.

De maneira opcional, quando uma expressão de agrupamento é fornecida, ela agrupa documentos com base nas expressões fornecidas e aplica funções de acumulador a cada grupo.

Sintaxe

Para agregações sem agrupamento, a etapa aggregate usa uma ou mais expressões de agregador com alias:

Node.js

const cities = await db.pipeline()
  .collection("/cities")
  .aggregate(
      countAll().as("total"),
      average("population").as("avg_population")
  )
  .execute();

Para agregações com agrupamento, são necessários outros grupos além dos agregadores:

Node.js

const result = await db.pipeline()
  .collectionGroup('citites')
  .aggregate({
    accumulators: [
      countAll().as('cities'),
      field('population').sum().as('total_popoluation')
    ],
    groups: [field('location.state').as('state')]
  })
  .execute();

Comportamento

Agregações sem agrupamento

Crie uma coleção cities com os seguintes documentos:

Node.js

await db.collection('cities').doc('SF').set({name: 'San Francisco', state: 'CA', country: 'USA', population: 870000});
await db.collection('cities').doc('LA').set({name: 'Los Angeles', state: 'CA', country: 'USA', population: 3970000});
await db.collection('cities').doc('NY').set({name: 'New York', state: 'NY', country: 'USA', population: 8530000});
await db.collection('cities').doc('TOR').set({name: 'Toronto', state: null, country: 'Canada', population: 2930000});
await db.collection('cities').doc('MEX').set({name: 'Mexico City', state: null, country: 'Mexico', population: 9200000});

Para saber o número total de cidades e a população média delas:

Node.js

const cities = await db.pipeline()
  .collection("/cities")
  .aggregate(
      countAll().as("total"),
      average("population").as("avg_population")
  )
  .execute();

que produz:

{avg_population: 5100000, total: 5}

Realizar agregações em grupos

Ao fornecer um argumento groups, é possível fazer agregações em cada grupo distinto.

Por exemplo, para encontrar a cidade com a maior população em cada país e estado:

Node.js

const cities = await db.pipeline()
  .collection("/cities")
  .aggregate({
      accumulators: [
          countAll().as("number_of_cities"),
          maximum("population").as("max_population")
      ],
      groups: ["country", "state"]
  })
  .execute();

que resulta em:

{country: "USA", state: "CA", max_population: 3970000, number_of_cities: 2},
{country: "USA", state: "NY", max_population: 8530000, number_of_cities: 1},
{country: "Canada", state: null, max_population: 2930000, number_of_cities: 1},
{country: "Mexico", state: null,  max_population: 9200000, number_of_cities: 1}

Expressões complexas no agrupamento

Além de agrupar apenas por valores de campo, a etapa aggregate permite agrupar por resultados de expressões complexas. Qualquer expressão válida em uma etapa select pode ser usada como uma chave de agrupamento. Isso permite um agrupamento flexível com base em valores ou condições calculados.

Por exemplo, para agrupar por se o campo de estado é nulo e descobrir a população total em cada grupo:

Node.js

const cities = await db.pipeline()
  .collection("/cities")
  .aggregate({
      accumulators: [
         sum("population").as("total_population")
      ],
      groups: [equal(field("state"), null).as("state_is_null")]
  })
  .execute();

Retornará:

{state_is_null: true, total_population: 12130000}
{state_is_null: false, total_population: 13370000}

Comportamentos do agregador

O comportamento de agregação de cada função compatível (por exemplo, count, sum, avg) pode ser encontrado na página dedicada a Funções de agregação.

Agrupar principais comportamentos

Ao agrupar documentos, o Firestore usa semântica de igualdade para determinar se os valores pertencem ao mesmo grupo.

Isso significa que valores equivalentes, por exemplo, valores numéricos matematicamente equivalentes, independentemente do tipo original (número inteiro de 32 bits, número inteiro de 64 bits, números de ponto flutuante, decimal128 etc.), são agrupados.

Por exemplo, em uma coleção numerics com diferentes documentos que contêm valores foo de número inteiro de 32 bits 1, número inteiro de 64 bits 1L e número de ponto flutuante 1.0, respectivamente, todos serão acumulados no mesmo grupo. Executar uma contagem agrupada por foo vai retornar:

{foo: 1.0, count: 3}

Nesses casos, em que há valores equivalentes diferentes no conjunto de dados, o valor de saída do grupo pode ser qualquer um desses valores equivalentes. Neste exemplo, foo pode ser 1, 1L ou 1.0.

Mesmo que pareça determinista, não tente confiar no comportamento de um valor específico sendo selecionado.

Uso da memória

A execução da agregação depende dos índices disponíveis. Quando não há um índice adequado escolhido pelo otimizador de consultas, o agregado precisa armazenar em buffer todos os grupos na memória.

Se você tiver um número muito grande de grupos ou se cada grupo for muito grande (por exemplo, agrupamento por valores enormes), essa etapa poderá ficar sem memória.

Nesses casos, aplique filtros para limitar o conjunto de dados a ser agregado, agrupe em campos menores/em menor número ou crie índices conforme recomendado para evitar usos grandes de memória. A explicação da consulta fornece informações sobre o plano de execução real da consulta e dados de criação de perfil para ajudar na depuração.