Menu Docs
Página inicial do Docs
/ / /
Kotlin Coroutine
/

Operações de expressão de aggregation

Nesta página

  • Visão geral
  • Como usar operações
  • Métodos do construtor
  • operações
  • Operações aritméticas
  • Operações de array
  • Operações booleanas
  • Operações de comparação
  • Operações condicionais
  • Operações de conveniência
  • Operações de conversão
  • Operações de datas
  • Operações de documentos
  • Operações de mapas
  • Operações de strings
  • Operações de verificação de tipo

Neste guia, você pode aprender como usar o Driver Kotlin do MongoDB para construir expressões para uso em pipelines de agregação. Você pode executar operações de expressão com métodos Java detectáveis e seguros por tipo em vez de documentos BSON. Como esses métodos seguem o padrão de interface fluente, você pode encadear operações de agregação para criar um código mais compacto e mais legível.

As operações neste guia utilizam métodos do pacote com.mongodb.client.model.mql. Esses métodos fornecem uma maneira idiomática de usar a API de Consulta, o mecanismo pelo qual o driver interage com uma implantação do MongoDB. Para saber mais sobre a API de consulta, consulte a documentação manual do servidor.

Os exemplos deste guia pressupõem que você inclua as seguintes importações em seu código:

import com.mongodb.client.model.Aggregates
import com.mongodb.client.model.Accumulators
import com.mongodb.client.model.Projections
import com.mongodb.client.model.Filters
import com.mongodb.client.model.mql.MqlValues

Para acessar campos de documento em uma expressão, você precisa fazer referência ao documento atual que está sendo processado pelo pipeline de agregação. Use o método current() para fazer referência a este documento. Para acessar o valor de um campo, você deve utilizar o método adequadamente digitado, como getString() ou getDate(). Ao especificar o tipo para um campo, você garante que o driver forneça apenas os métodos compatíveis com esse tipo. O seguinte código mostra como referenciar um campo de string denominado name:

current().getString("name")

Para especificar um valor em uma operação, passe para o método do construtor do of() para convertê-lo em um tipo válido. O seguinte código mostra como referenciar um valor de 1.0:

of(1.0)

Para criar uma operação, encadeie um método à sua referência de campo ou valor. Você pode criar operações mais complexas encadeando métodos adicionais.

O exemplo abaixo cria uma operação para encontrar pacientes no estado do Novo México, nos EUA, que foram a um consultório médico pelo menos uma vez. A operação executa as seguintes ações:

  • Verifica se o tamanho da matriz visitDates é maior que 0 usando o método gt()

  • Verifica se o valor do campo state é "Novo México" usando o método eq()

O método and() vincula estas operações para que o estágio do pipeline corresponda apenas a documentos que atendam aos dois critérios.

current()
.getArray("visitDates")
.size()
.gt(of(0))
.and(current()
.getString("state")
.eq(of("New Mexico")))

Enquanto alguns estágios de agregação, como group(), aceitam operações diretamente, outros estágios esperam que você primeiro inclua sua operação em um método como computed() ou expr(). Esses métodos, que aceitam valores do tipo TExpression, permitem usar suas expressões em determinadas agregações.

Para concluir o estágio do pipeline de agregação, inclua sua expressão em um método de construtor de agregados. A seguinte lista fornece exemplos de como incluir sua expressão em métodos de construtor de agregados comuns:

  • match(expr(<expression>))

  • project(fields(computed("<field name>", <expression>)))

  • group(<expression>)

Para saber mais sobre esses métodos, consulte oguia de aggregationdo .

Os exemplos utilizam o método listOf() para criar uma lista de estágios de agregação. Esta lista é passada para o método aggregate() de MongoCollection.

Você pode utilizar estes métodos do construtor para definir valores para uso em expressões de agregação Kotlin.

Método
Descrição
Referencia o documento atual sendo processado pela aggregation pipeline.
Referencia o documento atual sendo processado pelo pipeline de agregação como um valor de mapa.
Retorna um tipo de MqlValue correspondente à primitiva fornecida.
Retorna uma matriz de MqlValue tipos correspondentes à matriz fornecida de primitivos.
Retorna um valor de entrada.
Retorna um valor de mapa vazio.
Retorna o valor nulo conforme está na API de consulta.

Importante

Quando você fornece um valor a um desses métodos, o driver o trata literalmente. Por exemplo, of("$x") representa o valor de string "$x", em vez de um campo denominado x.

Consulte qualquer uma das seções em Operações para obter exemplos de uso desses métodos.

As seções a seguir fornecem informações e exemplos de operações de expressão de agregação disponíveis no driver. As operações são categorizadas por finalidade e funcionalidade.

Cada seção tem uma tabela que descreve os métodos de agregação disponíveis no driver e os operadores de expressão correspondentes na API de consulta. Os nomes dos métodos estão vinculados à documentação da API e os nomes dos operadores do pipeline de agregação estão vinculados a descrições e exemplos na documentação do manual do servidor. Embora cada método seja efetivamente equivalente à expressão correspondente da API de consulta, eles podem diferir nos parâmetros esperados e na implementação.

Observação

O driver gera uma expressão de API de query que pode ser diferente da expressão de API de query fornecida em cada exemplo. No entanto, ambas as expressões produzirão o mesmo resultado de agregação.

Importante

O driver não fornece métodos para todos os operadores de pipeline de agregação da API de consulta. Se você precisar usar uma operação sem suporte em uma agregação, será necessário definir a expressão inteira usando o tipo BSON Document. Para saber mais sobre o tipo Document, consulte Documentos.

Você pode executar uma operação aritmética em um valor do tipo MqlInteger ou MqlNumber utilizando os métodos descritos nesta seção.

Digamos que você tenha dados meteorológicos de um ano específico que inclua a medição de precipitação (em polegadas) para cada dia. Você quer encontrar a precipitação média, em milímetros, para cada mês.

O operador multiply() multiplica o campo precipitation por 25.4 para converter o valor em milímetros. O método acumulador avg() retorna a média como o campo avgPrecipMM. O método group() agrupa os valores por mês fornecidos no campo date de cada documento.

O seguinte código mostra o pipeline para essa agregação:

val month = current().getDate("date").month(of("UTC"))
val precip = current().getInteger("precipitation")
listOf(
Aggregates.group(
month,
Accumulators.avg("avgPrecipMM", precip.multiply(25.4))
))

O código a seguir fornece um pipeline de agregação equivalente na API de consulta:

[ { $group: {
_id: { $month: "$date" },
avgPrecipMM: {
$avg: { $multiply: ["$precipitation", 25.4] } }
} } ]

Você pode executar uma operação de array em um valor do tipo MqlArray utilizando os métodos descritos nesta seção.

Suponha que você tenha uma coleção de filmes, cada um dos quais contém uma array de documentos aninhados para os próximos horários de exibição. Cada documento aninhado contém uma array que representa o número total de assentos no teatro, em que a primeira entrada da array é o número de assentos premium e a segunda entrada é o número de assentos regulares. Cada documento aninhado também contém o número de ingressos que já foram comprados para o momento da exibição. Um documento nesta coleção pode se assemelhar ao seguinte:

{
"_id": ...,
"movie": "Hamlet",
"showtimes": [
{
"date": "May 14, 2023, 12:00 PM",
"seats": [ 20, 80 ],
"ticketsBought": 100
},
{
"date": "May 20, 2023, 08:00 PM",
"seats": [ 10, 40 ],
"ticketsBought": 34
}]
}

O método filter() exibe somente os resultados que correspondem ao predicado fornecido. Nesse caso, o predicado usa sum() para calcular o número total de assentos e compara esse valor com o número de ticketsBought com lt(). O método project() armazena esses resultados filtrados como uma nova array availableShowtimes.

Dica

É necessário especificar o tipo da array que você recupera com o método getArray() se precisar trabalhar com os valores da array como seu tipo específico.

Neste exemplo, especificamos que a array seats contém valores do tipo MqlDocument para que possamos extrair campos aninhados de cada entrada da array.

O seguinte código mostra o pipeline para essa agregação:

val showtimes = current().getArray<MqlDocument>("showtimes")
listOf(
Aggregates.project(
Projections.fields(
Projections.computed("availableShowtimes", showtimes
.filter { showtime ->
val seats = showtime.getArray<MqlInteger>("seats")
val totalSeats = seats.sum { n -> n }
val ticketsBought = showtime.getInteger("ticketsBought")
val isAvailable = ticketsBought.lt(totalSeats)
isAvailable
})
)))

Observação

Para melhorar a legibilidade, o exemplo anterior atribui valores intermediários às variáveis totalSeats e isAvailable. Se você não extrair esses valores intermediários em variáveis, o código ainda produzirá resultados equivalentes.

O código a seguir fornece um pipeline de agregação equivalente na API de consulta:

[ { $project: {
availableShowtimes: {
$filter: {
input: "$showtimes",
as: "showtime",
cond: { $lt: [ "$$showtime.ticketsBought", { $sum: "$$showtime.seats" } ] }
} }
} } ]

Você pode executar uma operação booleana em um valor do tipo MqlBoolean utilizando os métodos descritos nesta seção.

Método
Operador de pipeline de agregação

Suponha que você queira classificar leituras de temperatura muito baixa ou alta (em graus Fahrenheit) como extremas.

O operador or() verifica se as temperaturas são extremas comparando o campo temperature com valores pré-definidos com lt() e gt(). O método project() registra este resultado no campo extremeTemp.

O seguinte código mostra o pipeline para essa agregação:

val temperature = current().getInteger("temperature")
listOf(
Aggregates.project(
Projections.fields(
Projections.computed("extremeTemp", temperature
.lt(of(10))
.or(temperature.gt(of(95))))
)))

O código a seguir fornece um pipeline de agregação equivalente na API de consulta:

[ { $project: {
extremeTemp: { $or: [ { $lt: ["$temperature", 10] },
{ $gt: ["$temperature", 95] } ] }
} } ]

Você pode executar uma operação de comparação em um valor do tipo MqlValue utilizando os métodos descritos nesta seção.

Dica

O método cond() é semelhante ao operador ternário em Java e você deve usá-lo para ramificações simples com base em um valor booleano. Você deve usar os métodos switchOn() para comparações mais complexas, como a realização de correspondência de padrões no tipo de valor ou outras verificações arbitrárias no valor.

O exemplo a seguir mostra um pipeline que corresponde a todos os documentos onde o campo location tem o valor "California":

val location = current().getString("location")
listOf(
Aggregates.match(
Filters.expr(location.eq(of("California")))
))

O código a seguir fornece um pipeline de agregação equivalente na API de consulta:

[ { $match: { location: { $eq: "California" } } } ]

Você pode executar uma operação condicional utilizando os métodos descritos nesta seção.

Suponha que você tenha uma collection de clientes com suas informações de adesão. Originalmente, os clientes eram membros ou não eram membros. Com o tempo, os níveis de adesão foram introduzidos e usados no mesmo campo. As informações armazenadas nesse campo podem ser de tipos diferentes; e recomendamos criar um valor padronizado que indique o nível de adesão.

O método switchOn() verifica cada cláusula em ordem. Se o valor corresponder ao tipo indicado pela cláusula, essa cláusula determinará o valor da string correspondente ao nível de associação. Se o valor original for uma string, ele representará o nível de associação e este valor será utilizado. Se o tipo de dados for booleano, ele retornará Gold ou Guest para o nível de associação. Se o tipo de dados for uma array, ele retornará a string mais recente da array que corresponde ao nível de associação mais recente. Se o campo member for um tipo desconhecido, o método switchOn() fornecerá um valor padrão de Guest.

O seguinte código mostra o pipeline para essa agregação:

val member = current().getField("member")
listOf(
Aggregates.project(
Projections.fields(
Projections.computed("membershipLevel",
member.switchOn{field -> field
.isString{s-> s}
.isBoolean{b -> b.cond(of("Gold"), of("Guest"))}
.isArray { a -> a.last()}
.defaults{ d -> of("Guest")}})
)))

O código a seguir fornece um pipeline de agregação equivalente na API de consulta:

[ { $project: {
membershipLevel: {
$switch: {
branches: [
{ case: { $eq: [ { $type: "$member" }, "string" ] }, then: "$member" },
{ case: { $eq: [ { $type: "$member" }, "bool" ] }, then: { $cond: {
if: "$member",
then: "Gold",
else: "Guest" } } },
{ case: { $eq: [ { $type: "$member" }, "array" ] }, then: { $last: "$member" } }
],
default: "Guest" } }
} } ]

Você pode aplicar funções personalizadas aos valores do tipo MqlValue utilizando os métodos descritos nesta seção.

Para melhorar a legibilidade e permitir a reutilização do código, mova o código redundante para métodos estáticos. No entanto, não é possível encadear métodos estáticos diretamente em Kotlin. O método passTo() permite encadear valores em métodos estáticos personalizados.

Método
Operador de pipeline de agregação
Nenhum operador correspondente

Digamos que você precise determinar o desempenho de uma turma em relação a alguns parâmetros. Você deseja encontrar a nota final média para cada turma e compará-la com os valores de parâmetro.

O método personalizado gradeAverage() a seguir recebe uma array de documentos e o nome de um campo inteiro compartilhado entre esses documentos. Ele calcula a média desse campo em todos os documentos da array fornecida e determina a média desse campo em todos os elementos da array fornecida. O método evaluate() compara um valor fornecido com dois limites de intervalo fornecidos e gera uma string de resposta com base na comparação dos valores:

fun gradeAverage(students: MqlArray<MqlDocument>, fieldName: String): MqlNumber {
val sum = students.sum{ student -> student.getInteger(fieldName) }
val avg = sum.divide(students.size())
return avg
}
fun evaluate(grade: MqlNumber, cutoff1: MqlNumber, cutoff2: MqlNumber): MqlString {
val message = grade.switchOn{ on -> on
.lte(cutoff1) { g -> of("Needs improvement") }
.lte(cutoff2) { g -> of("Meets expectations") }
.defaults{g -> of("Exceeds expectations")}}
return message
}

Dica

Uma vantagem de utilizar o método passTo() é que você pode reutilizar seus métodos personalizados para outras agregações. Você poderia usar o método gradeAverage() para encontrar a média das notas de grupos de alunos filtrados, por exemplo, pelo ano de ingresso ou pelo distrito, e não apenas pela turma. Você pode usar o método evaluate() para avaliar, por exemplo, o desempenho de um aluno específico ou o desempenho de certa escola ou distrito como um todo.

O método passArrayTo() junta todos os alunos e calcula a pontuação média utilizando o método gradeAverage(). Em seguida, o método passNumberTo() utiliza o método evaluate() para determinar o desempenho da turma. Este exemplo armazena o resultado no campo evaluation usando o método project().

O seguinte código mostra o pipeline para essa agregação:

val students = current().getArray<MqlDocument>("students")
listOf(
Aggregates.project(
Projections.fields(
Projections.computed("evaluation", students
.passArrayTo { s -> gradeAverage(s, "finalGrade") }
.passNumberTo { grade -> evaluate(grade, of(70), of(85)) })
)))

O código a seguir fornece um pipeline de agregação equivalente na API de consulta:

[ { $project: {
evaluation: { $switch: {
branches: [
{ case: { $lte: [ { $avg: "$students.finalGrade" }, 70 ] },
then: "Needs improvement"
},
{ case: { $lte: [ { $avg: "$students.finalGrade" }, 85 ] },
then: "Meets expectations"
}
],
default: "Exceeds expectations" } }
} } ]

Você pode executar uma operação de conversão para converter entre determinados tipos de MqlValue utilizando os métodos descritos nesta seção.

Suponha que você queira ter uma coleção de dados dos alunos que inclua seus anos de graduação, que são armazenados como strings. Você deseja calcular o ano da reunião de cinco anos e armazenar este valor em um novo campo.

O método parseInteger() converte o graduationYear para um número inteiro de forma que add() possa calcular o ano de reunião. O método addFields() armazena este resultado como um novo campo reunionYear.

O seguinte código mostra o pipeline para essa agregação:

val graduationYear = current().getString("graduationYear")
listOf(
Aggregates.addFields(
Field("reunionYear",
graduationYear
.parseInteger()
.add(5))
))

O código a seguir fornece um pipeline de agregação equivalente na API de consulta:

[ { $addFields: {
reunionYear: {
$add: [ { $toInt: "$graduationYear" }, 5 ] }
} } ]

Você pode executar uma operação de data em um valor do tipo MqlDate usando os métodos descritos nesta seção.

Suponha que você tenha dados sobre entregas de pacotes e precise fazer a correspondência entre as entregas que ocorreram em qualquer segunda-feira no fuso horário "America/New_York".

Se o campo deliveryDate contiver qualquer string que represente datas válidas, como "2018-01-15T16:00:00Z" ou "Jan 15, 2018, 12:00 PM EST", você poderá usar o método parseDate() para converter as strings em tipos de data.

O método dayOfWeek() determina qual é o dia da semana e o converte em um número com base em qual dia é uma segunda-feira, de acordo com o parâmetro "America/New_York". O método eq() compara esse valor com 2, que corresponde a Monday com base no parâmetro de fuso horário fornecido.

O seguinte código mostra o pipeline para essa agregação:

val deliveryDate = current().getString("deliveryDate")
listOf(
Aggregates.match(
Filters.expr(deliveryDate
.parseDate()
.dayOfWeek(of("America/New_York"))
.eq(of(2))
)))

O código a seguir fornece um pipeline de agregação equivalente na API de consulta:

[ { $match: {
$expr: {
$eq: [ {
$dayOfWeek: {
date: { $dateFromString: { dateString: "$deliveryDate" } },
timezone: "America/New_York" }},
2
] }
} } ]

Você pode executar uma operação de documento em um valor do tipo MqlDocument usando os métodos descritos nesta seção.

Digamos que você tenha um conjunto de dados de clientes antigos que contém endereços como documentos secundários no campo mailing.address. Você deseja encontrar todos os clientes que moram atualmente no estado de Washington. Um documento desse conjunto poderia ser como o exemplo abaixo:

{
"_id": ...,
"customer.name": "Mary Kenneth Keller",
"mailing.address":
{
"street": "601 Mongo Drive",
"city": "Vasqueztown",
"state": "CO",
"zip": 27017
}
}

O método getDocument() recupera o campo mailing.address como um documento para que o campo state aninhado possa ser recuperado com o método getString() . O método eq() verifica se o valor do campo state é "WA".

O seguinte código mostra o pipeline para essa agregação:

val address = current().getDocument("mailing.address")
listOf(
Aggregates.match(
Filters.expr(address
.getString("state")
.eq(of("WA"))
)))

O código a seguir fornece um pipeline de agregação equivalente na API de consulta:

[
{ $match: {
$expr: {
$eq: [{
$getField: {
input: { $getField: { input: "$$CURRENT", field: "mailing.address"}},
field: "state" }},
"WA" ]
}}}]

Você pode executar uma operação de mapa em um valor de tipo MqlMap ou MqlEntry utilizando os métodos descritos nesta seção.

Dica

Você deve representar dados como um mapa se os dados mapear chaves arbitrárias como datas ou IDs de item para valores.

Método
Operador de pipeline de agregação
Nenhum operador correspondente
Nenhum operador correspondente
Nenhum operador correspondente
Nenhum operador correspondente
Nenhum operador correspondente
Nenhum operador correspondente
Nenhum operador correspondente
Nenhum operador correspondente
Nenhum operador correspondente

Suponha que você tenha uma coleção de dados de estoque em que cada documento representa um item individual que você é responsável por fornecer. Cada documento contém um campo que é um mapa de todos os seus armazéns e quantas cópias eles têm atualmente em seu inventário do item. Você deseja determinar o número total de cópias de itens que você tem em todos os seus armazéns. Um documento nesta coleção pode se assemelhar ao seguinte:

{
"_id": ...,
"item": "notebook"
"warehouses": [
{ "Atlanta", 50 },
{ "Chicago", 0 },
{ "Portland", 120 },
{ "Dallas", 6 }
]
}

O método entries() retorna as entradas do mapa no campo warehouses como uma array. O método sum() calcula o valor total dos itens baseado nos valores na array recuperada com o método getValue() . Este exemplo armazena o resultado como o novo campo totalInventory utilizando o método project().

O seguinte código mostra o pipeline para essa agregação:

val warehouses = current().getMap<MqlNumber>("warehouses")
listOf(
Aggregates.project(
Projections.fields(
Projections.computed("totalInventory", warehouses
.entries()
.sum { v -> v.getValue() })
)))

O código a seguir fornece um pipeline de agregação equivalente na API de consulta:

[ { $project: {
totalInventory: {
$sum: {
$getField: { $objectToArray: "$warehouses" },
} }
} } ]

Você pode executar uma operação de string em um valor do tipo MqlString utilizando os métodos descritos nesta seção.

Suponha que você precise gerar nomes de usuário em letras minúsculas para os funcionários de uma empresa a partir dos sobrenomes e IDs dos funcionários.

O método append() combina os campos lastName e employeeID em um único nome de usuário, enquanto o método toLower() torna todo o nome de usuário minúsculo. Este exemplo armazena o resultado como um novo campo username utilizando o método project().

O seguinte código mostra o pipeline para essa agregação:

val lastName = current().getString("lastName")
val employeeID = current().getString("employeeID")
listOf(
Aggregates.project(
Projections.fields(
Projections.computed("username", lastName
.append(employeeID)
.toLower())
)))

O código a seguir fornece um pipeline de agregação equivalente na API de consulta:

[ { $project: {
username: {
$toLower: { $concat: ["$lastName", "$employeeID"] } }
} } ]

Você pode executar uma operação de verificação de tipo em um valor do tipo MqlValue utilizando os métodos descritos nesta seção.

Estes métodos não retornam valores booleanos. Em vez disso, você fornece um valor padrão que corresponde ao tipo especificado pelo método. Se o valor marcado corresponder ao tipo de método, o valor marcado será retornado. Caso contrário, o valor predefinido fornecido é devolvido. Se você deseja programar lógica de ramificação com base no tipo de dados, consulte switchOn().

Método
Operador de pipeline de agregação
Nenhum operador correspondente
Nenhum operador correspondente
Nenhum operador correspondente
Nenhum operador correspondente
Nenhum operador correspondente
Nenhum operador correspondente
Nenhum operador correspondente
Nenhum operador correspondente

Suponha que você tenha uma collection de dados de classificação. Uma versão anterior do esquema de avaliação permitia que os usuários enviassem avaliações negativas sem uma classificação por estrelas. Recomendamos converter essas avaliações negativas sem uma classificação por estrelas para ter o valor mínimo de 1 estrela.

O método isNumberOr() retorna o valor de rating ou um valor de 1 se rating não for um número ou for nulo. O método project() retorna este valor como um novo campo numericalRating.

O seguinte código mostra o pipeline para essa agregação:

val rating = current().getField("rating")
listOf(
Aggregates.project(
Projections.fields(
Projections.computed("numericalRating", rating
.isNumberOr(of(1)))
)))

O código a seguir fornece um pipeline de agregação equivalente na API de consulta:

[ { $project: {
numericalRating: {
$cond: { if: { $isNumber: "$rating" },
then: "$rating",
else: 1
} }
} } ]

Voltar

Agregação