Docs Menu
Docs Home
/ /
/ / /

Operaciones de expresión de agregación

En esta guía, aprenderá a usar el controlador Kotlin de MongoDB para construir expresiones que se usarán en pipelines de agregación. Puede realizar operaciones de expresión con métodos Java detectables y con seguridad de tipos, en lugar de documentos BSON. Dado que estos métodos siguen el patrón de interfaz fluida, puede encadenar operaciones de agregación para crear código más compacto y legible.

Las operaciones de esta guía utilizan métodos de la Paquete com.mongodb.client.model.mql. Estos métodos proporcionan una forma idiomática de usar la API de consulta, el mecanismo mediante el cual el controlador interactúa con una implementación de MongoDB. Para obtener más información sobre la API de consulta, consulte la documentación del manual del servidor.

Los ejemplos de esta guía suponen que incluye las siguientes importaciones en su 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 acceder a los campos de documento en una expresión, debe hacer referencia al documento actual que está procesando la canalización de agregación. Utilice el current() Método para hacer referencia a este documento. Para acceder al valor de un campo, debe usar el método con el tipo adecuado, como getString() o getDate(). Al especificar el tipo de un campo, se asegura de que el controlador solo proporcione los métodos compatibles con dicho tipo. El siguiente código muestra cómo hacer referencia a un campo de cadena llamado name:

current().getString("name")

Para especificar un valor en una operación, páselo al método constructor of() para convertirlo a un tipo válido. El siguiente código muestra cómo referenciar un valor de 1.0:

of(1.0)

Para crear una operación, encadene un método a su campo o referencia de valor. Puede crear operaciones más complejas encadenando métodos adicionales.

El siguiente ejemplo crea una operación para encontrar pacientes en Nuevo México que hayan ido al consultorio del doctor al menos una vez. La operación realiza las siguientes acciones:

  • Comprueba si el tamaño de la matriz visitDates es mayor que 0 utilizando el método gt()

  • Comprueba si el valor del campo state es “Nuevo México” utilizando el método eq()

El método and() vincula estas operaciones para que la etapa de canalización coincida únicamente con los documentos que cumplen ambos criterios.

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

Mientras que algunas etapas de agregación, como group(), aceptan operaciones directamente, otras esperan que primero incluya la operación en un método como computed() o expr(). Estos métodos, que toman valores de tipo TExpression, permiten usar expresiones en ciertas agregaciones.

Para completar la etapa de canalización de agregación, incluya su expresión en un método de generación de agregados. La siguiente lista muestra ejemplos de cómo incluir su expresión en métodos comunes de generación de agregados:

  • match(expr(<expression>))

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

  • group(<expression>)

Para obtener más información sobre estos métodos, consulte la Guía de agregación.

Los ejemplos utilizan el método listOf() para crear una lista de etapas de agregación. Esta lista se pasa al método aggregate() de MongoCollection.

Puede utilizar estos métodos constructores para definir valores para usar en expresiones de agregación de Kotlin.

Método
Descripción

Hace referencia al documento actual que está siendo procesado por el canal de agregación.

Hace referencia al documento actual que está siendo procesado por el canal de agregación como un valor de mapa.

Devuelve un tipo MqlValue correspondiente al primitivo proporcionado.

Devuelve una matriz de tipos MqlValue correspondiente a la matriz de primitivos proporcionada.

Devuelve un valor de entrada.

Devuelve un mapa vacío.

Devuelve el valor nulo tal como existe en la API de consulta.

Importante

Al asignar un valor a uno de estos métodos, el controlador lo interpreta literalmente. Por ejemplo, of("$x") representa el valor de la cadena "$x", en lugar de un campo llamado x.

Consulte cualquiera de las secciones de Operaciones para ver ejemplos que utilizan estos métodos.

Las siguientes secciones proporcionan información y ejemplos de las operaciones de expresión de agregación disponibles en el controlador. Las operaciones se clasifican por propósito y funcionalidad.

Cada sección incluye una tabla que describe los métodos de agregación disponibles en el controlador y los operadores de expresión correspondientes en la API de consulta. Los nombres de los métodos enlazan con la documentación de la API, y los nombres de los operadores de la canalización de agregación enlazan con descripciones y ejemplos en la documentación del manual del servidor. Si bien cada método es equivalente a la expresión de la API de consulta correspondiente, pueden diferir en los parámetros esperados y la implementación.

Nota

El controlador genera una expresión de la API de consulta que puede ser diferente de la proporcionada en cada ejemplo. Sin embargo, ambas expresiones producirán el mismo resultado de agregación.

Importante

El controlador no proporciona métodos para todos los operadores de canalización de agregación en la API de consulta. Si necesita usar una operación no compatible en una agregación, debe definir la expresión completa con el Document tipo BSON. Para obtener más información sobre el Document tipo, consulte la documentación.

Puede realizar una operación aritmética en un valor de tipo MqlInteger o MqlNumber utilizando los métodos descritos en esta sección.

Supongamos que tiene datos meteorológicos de un año específico que incluyen la precipitación diaria (en pulgadas). Quiere calcular la precipitación media, en milímetros, de cada mes.

El operador multiply() multiplica el campo precipitation por 25.4 para convertir el valor a milímetros. El método acumulador avg() devuelve el promedio como el campo avgPrecipMM. El método group() agrupa los valores por mes indicados en el campo date de cada documento.

El siguiente código muestra la canalización para esta agregación:

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

El siguiente código proporciona una canalización de agregación equivalente en la API de consulta:

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

Puede realizar una operación de matriz en un valor de tipo MqlArray utilizando los métodos descritos en esta sección.

Supongamos que tiene una colección de películas, cada una de las cuales contiene una matriz de documentos anidados para las próximas funciones. Cada documento anidado contiene una matriz que representa el número total de asientos en la sala, donde la primera entrada de la matriz representa el número de asientos premium y la segunda, el número de asientos regulares. Cada documento anidado también contiene el número de entradas ya adquiridas para la función. Un documento de esta colección podría ser similar al siguiente:

{
"_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
}]
}

El método filter() muestra solo los resultados que coinciden con el predicado proporcionado. En este caso, el predicado usa sum() para calcular el número total de asientos y compara ese valor con el número de ticketsBought con lt(). El método project() almacena estos resultados filtrados como una nueva matriz availableShowtimes.

Tip

Debe especificar el tipo de matriz que recupera con el método getArray() si necesita trabajar con los valores de la matriz como su tipo específico.

En este ejemplo, especificamos que la matriz seats contiene valores de tipo MqlDocument para que podamos extraer campos anidados de cada entrada de la matriz.

El siguiente código muestra la canalización para esta agregación:

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
})
)))

Nota

Para mejorar la legibilidad, el ejemplo anterior asigna valores intermedios a las variables totalSeats y isAvailable. Si no se extraen estos valores intermedios en las variables, el código sigue produciendo resultados equivalentes.

El siguiente código proporciona una canalización de agregación equivalente en la API de consulta:

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

Puede realizar una operación booleana en un valor de tipo MqlBoolean utilizando los métodos descritos en esta sección.

Método
Operador de tubería de agregación

Supongamos que desea clasificar lecturas de temperaturas climáticas muy bajas o muy altas (en grados Fahrenheit) como extremas.

El operador or() comprueba si las temperaturas son extremas comparando el campo temperature con los valores predefinidos lt() y gt(). El método project() registra este resultado en el campo extremeTemp.

El siguiente código muestra la canalización para esta agregación:

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

El siguiente código proporciona una canalización de agregación equivalente en la API de consulta:

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

Puede realizar una operación de comparación en un valor de tipo MqlValue utilizando los métodos descritos en esta sección.

Tip

El método cond() es similar al operador ternario de Java y se recomienda usarlo para ramificaciones simples basadas en un valor booleano. Se recomienda usar los métodos switchOn() para comparaciones más complejas, como la búsqueda de patrones en el tipo de valor u otras comprobaciones arbitrarias del valor.

El siguiente ejemplo muestra una canalización que coincide con todos los documentos donde el campo location tiene el valor "California":

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

El siguiente código proporciona una canalización de agregación equivalente en la API de consulta:

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

Puede realizar una operación condicional utilizando los métodos descritos en esta sección.

Supongamos que tiene una colección de clientes con su información de membresía. Originalmente, los clientes eran miembros o no. Con el tiempo, se introdujeron los niveles de membresía y se utilizó el mismo campo. La información almacenada en este campo puede ser de varios tipos diferentes, y desea crear un valor estandarizado que indique su nivel de membresía.

El método switchOn() revisa cada cláusula en orden. Si el valor coincide con el tipo indicado por la cláusula, esta determina el valor de cadena correspondiente al nivel de membresía. Si el valor original es una cadena, representa el nivel de membresía y se utiliza ese valor. Si el tipo de dato es booleano, devuelve Gold o Guest para el nivel de membresía. Si el tipo de dato es una matriz, devuelve la cadena más reciente de la matriz que coincida con el nivel de membresía más reciente. Si el campo member es de tipo desconocido, el método switchOn() proporciona un valor predeterminado de Guest.

El siguiente código muestra la canalización para esta agregación:

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")}})
)))

El siguiente código proporciona una canalización de agregación equivalente en la 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" } }
} } ]

Puedes aplicar funciones personalizadas a valores de tipo MqlValue utilizando los métodos descritos en esta sección.

Para mejorar la legibilidad y permitir la reutilización del código, puedes mover el código redundante a métodos estáticos. Sin embargo, no es posible encadenar directamente métodos estáticos en Kotlin. El método passTo() permite encadenar valores a métodos estáticos personalizados.

Método
Operador de tubería de agregación

No hay operador correspondiente

Supongamos que necesita determinar el rendimiento de una clase en relación con ciertos parámetros. Quiere encontrar la calificación final promedio de cada clase y compararla con los valores de referencia.

El siguiente método personalizado gradeAverage() toma una matriz de documentos y el nombre de un campo entero compartido entre ellos. Calcula el promedio de ese campo en todos los documentos de la matriz proporcionada y determina el promedio de ese campo en todos los elementos de la matriz proporcionada. El método evaluate() compara un valor proporcionado con dos límites de rango proporcionados y genera una cadena de respuesta basada en la comparación de los 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
}

Tip

Una ventaja de usar el método passTo() es que permite reutilizar los métodos personalizados para otras agregaciones. Se puede usar el método gradeAverage() para calcular el promedio de calificaciones de grupos de estudiantes, filtrando, por ejemplo, por año de ingreso o distrito, no solo por clase. Se puede usar el método evaluate() para evaluar, por ejemplo, el rendimiento de un estudiante individual o de toda una escuela o distrito.

El método passArrayTo() toma a todos los estudiantes y calcula la puntuación media mediante el método gradeAverage(). Posteriormente, el método passNumberTo() utiliza el método evaluate() para determinar el rendimiento de las clases. Este ejemplo almacena el resultado como el campo evaluation mediante el método project().

El siguiente código muestra la canalización para esta agregación:

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)) })
)))

El siguiente código proporciona una canalización de agregación equivalente en la 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" } }
} } ]

Puede realizar una operación de conversión para convertir entre ciertos tipos MqlValue utilizando los métodos descritos en esta sección.

Supongamos que desea tener una colección de datos de estudiantes que incluye sus años de graduación, almacenados como cadenas. Quiere calcular el año de su reunión de cinco años y almacenar este valor en un nuevo campo.

El método parseInteger() convierte el graduationYear en un número entero para que add() pueda calcular el año de la reunión. El método addFields() almacena este resultado como un nuevo campo reunionYear.

El siguiente código muestra la canalización para esta agregación:

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

El siguiente código proporciona una canalización de agregación equivalente en la API de consulta:

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

Puede realizar una operación de fecha en un valor de tipo MqlDate utilizando los métodos descritos en esta sección.

Supongamos que tiene datos sobre entregas de paquetes y necesita hacer coincidir las entregas que ocurrieron cualquier lunes en la zona horaria "America/New_York".

Si el campo deliveryDate contiene valores de cadena que representan fechas válidas, como "2018-01-15T16:00:00Z" o "Jan 15, 2018, 12:00 PM EST", puede utilizar el método parseDate() para convertir las cadenas en tipos de fecha.

El método dayOfWeek() determina el día de la semana y lo convierte en un número según el lunes, según el parámetro "America/New_York". El método eq() compara este valor con 2, que corresponde al lunes según el parámetro de zona horaria proporcionado.

El siguiente código muestra la canalización para esta agregación:

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

El siguiente código proporciona una canalización de agregación equivalente en la API de consulta:

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

Puede realizar una operación de documento en un valor de tipo MqlDocument utilizando los métodos descritos en esta sección.

Supongamos que tiene una colección de datos de clientes antiguos que incluye direcciones como documentos secundarios en el campo mailing.address. Quiere encontrar todos los clientes que actualmente residen en el estado de Washington. Un documento de esta colección podría parecerse al siguiente:

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

El método getDocument() recupera el campo mailing.address como documento, por lo que el campo state anidado se puede recuperar con el método getString(). El método eq() comprueba si el valor del campo state es "WA".

El siguiente código muestra la canalización para esta agregación:

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

El siguiente código proporciona una canalización de agregación equivalente en la API de consulta:

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

Puede realizar una operación de mapa en un valor de tipo MqlMap o MqlEntry utilizando los métodos descritos en esta sección.

Tip

Debes representar los datos como un mapa si los datos asignan claves arbitrarias, como fechas o identificadores de elementos, a valores.

Método
Operador de tubería de agregación

No hay operador correspondiente

No hay operador correspondiente

No hay operador correspondiente

No hay operador correspondiente

No hay operador correspondiente

No hay operador correspondiente

No hay operador correspondiente

No hay operador correspondiente

No hay operador correspondiente

Supongamos que tiene una colección de datos de inventario donde cada documento representa un artículo individual que usted es responsable de suministrar. Cada documento contiene un campo que representa un mapa de todos sus almacenes y cuántas copias tienen actualmente en inventario del artículo. Quiere determinar el número total de copias de artículos que tiene en todos sus almacenes. Un documento de esta colección podría ser similar al siguiente:

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

El método entries() devuelve las entradas del mapa en el campo warehouses como una matriz. El método sum() calcula el valor total de los elementos basándose en los valores de la matriz obtenidos con el método getValue(). Este ejemplo almacena el resultado como el nuevo campo totalInventory mediante el método project().

El siguiente código muestra la canalización para esta agregación:

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

El siguiente código proporciona una canalización de agregación equivalente en la API de consulta:

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

Puede realizar una operación de cadena en un valor de tipo MqlString utilizando los métodos descritos en esta sección.

Supongamos que necesita generar nombres de usuario en minúsculas para los empleados de una empresa a partir de los apellidos y las identificaciones de los empleados.

El método append() combina los campos lastName y employeeID en un solo nombre de usuario, mientras que el método toLower() convierte todo el nombre de usuario en minúsculas. Este ejemplo almacena el resultado como un nuevo campo username mediante el método project().

El siguiente código muestra la canalización para esta agregación:

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

El siguiente código proporciona una canalización de agregación equivalente en la API de consulta:

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

Puede realizar una operación de verificación de tipo en un valor de tipo MqlValue utilizando los métodos descritos en esta sección.

Estos métodos no devuelven valores booleanos. En su lugar, se proporciona un valor predeterminado que coincide con el tipo especificado por el método. Si el valor seleccionado coincide con el tipo del método, se devuelve. De lo contrario, se devuelve el valor predeterminado proporcionado. Si desea programar la lógica de ramificación según el tipo de dato, consulte switchOn().

Método
Operador de tubería de agregación

No hay operador correspondiente

No hay operador correspondiente

No hay operador correspondiente

No hay operador correspondiente

No hay operador correspondiente

No hay operador correspondiente

No hay operador correspondiente

No hay operador correspondiente

Supongamos que tiene una colección de datos de calificación. Una versión anterior del esquema de reseñas permitía a los usuarios enviar reseñas negativas sin calificación de estrellas. Quiere convertir cualquiera de estas reseñas negativas sin calificación de estrellas para que tengan un valor mínimo de 1 estrellas.

El método isNumberOr() devuelve el valor rating o 1 si rating no es un número o es nulo. El método project() devuelve este valor como un nuevo campo numericalRating.

El siguiente código muestra la canalización para esta agregación:

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

El siguiente código proporciona una canalización de agregación equivalente en la API de consulta:

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

Volver

Agregación

En esta página