Overview
En esta guía, puedes aprender cómo usar el Driver de Kotlin de MongoDB para construir expresiones para usarlas en pipelines de agregación. Puedes realizar operaciones de expresión con métodos Java detectables y seguros para tipos, en lugar de documentos BSON. Debido a que estos métodos siguen el patrón de interfaz fluida, puedes encadenar operaciones de agregación juntas para crear un código que sea tanto más compacto como más naturalmente legible.
Las operaciones de esta guía utilizan métodos del com.mongodb.client.model.mql paquete. Estos métodos proporcionan una forma idiomática de utilizar la API de query, el mecanismo mediante el cual el driver interactúa con una implementación de MongoDB. Para obtener más información sobre la API de query, consulta la documentación del manual del servidor.
Cómo utilizar operaciones
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 referirse a este documento. Para acceder al valor de un campo, debes usar el método apropiado según el tipo, como getString() o getDate(). Cuando especificas el tipo para un campo, aseguras que el controlador proporcione solo aquellos métodos que sean compatibles con ese tipo. El siguiente código muestra cómo hacer referencia a un campo string llamado name:
current().getString("name")
Para especificar un valor en una operación, pásalo al método constructor of() para convertirlo en un tipo válido. El código siguiente 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
visitDateses mayor que0utilizando el métodogt()Comprueba si el valor del campo
statees “Nuevo México” utilizando el métodoeq()
El método and() vincula estas operaciones para que la etapa de la pipeline coincida solo con los documentos que cumplan ambos criterios.
current() .getArray("visitDates") .size() .gt(of(0)) .and(current() .getString("state") .eq(of("New Mexico")))
Mientras que algunos etapas de agregación, como group(), aceptan operaciones directamente, otras etapas esperan que primero incluyas tu operación en un método como computed() o expr(). Estos métodos, que aceptan valores de tipo TExpression, permiten utilizar expresiones en ciertas agregaciones.
Para completar la etapa del pipeline de agregación, incluye tu expresión en un método del generador de agregados. La siguiente lista proporciona ejemplos de cómo incluir tu expresión en métodos comunes del builder 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.
Métodos constructores
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 la pipeline 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 | |
Devuelve una matriz de tipos | |
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 en Operaciones para ejemplos que utilizan estos métodos.
Operaciones
Las siguientes secciones proporcionan información y ejemplos acerca de las operaciones de expresiones de agregación disponibles en el controlador. Las operaciones se categorizan por propósito y funcionalidad.
Cada sección cuenta con 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 Query. Los nombres de los métodos enlazan con la documentación de la API y los nombres de los operadores de la pipeline de agregación enlazan con descripciones y ejemplos en la documentación del Manual del Servidor. Si bien cada método es esencialmente equivalente a la expresión correspondiente de Query API, pueden diferir en los parámetros esperados e 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.
Operaciones aritméticas
Puedes realizar una operación aritmética en un valor de tipo MqlInteger o MqlNumber utilizando los métodos descritos en esta sección.
Método | Operador de canalización de agregación |
|---|---|
Supongamos que tienes datos meteorológicos de un año específico que incluyen la medición de precipitaciones (en pulgadas) para cada día. Quieres encontrar la precipitación media, en milímetros, para cada mes.
El operador multiply() multiplica el campo precipitation por 25.4 para convertir el valor a milímetros. El método de acumulador avg() devuelve el promedio como el campo avgPrecipMM. El método group() agrupa los valores por el mes proporcionado en el campo date de cada documento.
El siguiente código muestra el pipeline 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 un pipeline de agregación equivalente en la Query API:
[ { $group: { _id: { $month: "$date" }, avgPrecipMM: { $avg: { $multiply: ["$precipitation", 25.4] } } } } ]
Operaciones de arreglos
Puedes realizar una operación de arreglo en un valor de tipo MqlArray utilizando los métodos descritos en esta sección.
Método | Operador de canalización de agregación |
|---|---|
Supón que cuentas con una colección de películas, cada una de las cuales contiene un arreglo de documentos anidados con los próximos horarios de funciones. Cada documento anidado contiene un arreglo que representa el número total de asientos en el teatro, donde la primera entrada del arreglo es el número de asientos premium y la segunda entrada es el número de asientos regulares. Cada documento anidado también contiene el número de tickets que ya se han comprado para la función. Un documento en esta colección podría parecerse 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() solo muestra los resultados que coinciden con el predicado proporcionado. En este caso, el predicado utiliza sum() para calcular el número total de plazas y compara ese valor con el número de ticketsBought con lt(). El método project() almacena estos resultados filtrados como un nuevo arreglo availableShowtimes.
Tip
Se debe especificar el tipo de arreglo que se recupera con el método getArray() si se necesita trabajar con los valores del arreglo 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 el pipeline 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 extrae estos valores intermedios en variables, el código aún produce resultados equivalentes.
El siguiente código proporciona un pipeline de agregación equivalente en la Query API:
[ { $project: { availableShowtimes: { $filter: { input: "$showtimes", as: "showtime", cond: { $lt: [ "$$showtime.ticketsBought", { $sum: "$$showtime.seats" } ] } } } } } ]
Operaciones booleanas
Puede realizar una operación booleana en un valor de tipo MqlBoolean utilizando los métodos descritos en esta sección.
Supón que quieres clasificar las mediciones de temperatura atmosférica muy bajas o altas (en grados Fahrenheit) como extremas.
El operador or() verifica si las temperaturas son extremas comparando el campo temperature con valores predefinidos usando lt() y gt(). El método project() registra este resultado en el campo extremeTemp.
El siguiente código muestra el pipeline 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 un pipeline de agregación equivalente en la Query API:
[ { $project: { extremeTemp: { $or: [ { $lt: ["$temperature", 10] }, { $gt: ["$temperature", 95] } ] } } } ]
Operaciones de comparación
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 en Java y deberías usarlo para ramas sencillas basadas en un valor booleano. Deberías utilizar los métodos switchOn() para comparaciones más complejas, como realizar coincidencias de patrones en el tipo de valor u otros controles arbitrarios sobre el valor.
Método | Operador de canalización de agregación |
|---|---|
El siguiente ejemplo muestra un pipeline 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 un pipeline de agregación equivalente en la Query API:
[ { $match: { location: { $eq: "California" } } } ]
Operaciones condicionales
Puede realizar una operación condicional utilizando los métodos descritos en esta sección.
Método | Operador de canalización de agregació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() verifica cada cláusula por orden. Si el valor coincide con el tipo indicado por la cláusula, esa cláusula determina el valor de string correspondiente al nivel de membresía. Si el valor original es una string, representa el nivel de membresía y se utiliza ese valor. Si el tipo de dato es booleano, retorna Gold o Guest para el nivel de membresía. Si el tipo de dato es un arreglo, retorna el string más reciente del arreglo que coincide con el nivel de membresía más reciente. Si el campo member es de un tipo desconocido, el método switchOn() proporciona un valor por defecto de Guest.
El siguiente código muestra el pipeline 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 un pipeline de agregación equivalente en la Query API:
[ { $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" } } } } ]
Operaciones Convenientes
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, puede 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() te permite encadenar valores en métodos estáticos personalizados.
Método | Operador de canalización de agregación |
|---|---|
No hay operador correspondiente |
Supongamos que necesita determinar cómo se está desempeñando una clase frente a ciertos puntos de referencia. Quiere encontrar la calificación final promedio para 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 todos los estudiantes y calcula la puntuación promedio utilizando el método gradeAverage(). Luego, 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 utilizando el método project().
El siguiente código muestra el pipeline 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 un pipeline de agregación equivalente en la Query API:
[ { $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" } } } } ]
Operaciones de conversión
Puede realizar una operación de conversión para convertir entre ciertos tipos MqlValue utilizando los métodos descritos en esta sección.
Método | Operador de canalización de agregación |
|---|---|
No hay operador correspondiente | |
No hay operador correspondiente | |
Supongamos que deseas tener una colección de datos estudiantiles que incluya sus años de graduación, los cuales se almacenan como cadenas de texto. Se desea calcular el año de su reunión de cinco años y almacenar este valor en un campo nuevo.
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 el pipeline para esta agregación:
val graduationYear = current().getString("graduationYear") listOf( Aggregates.addFields( Field("reunionYear", graduationYear .parseInteger() .add(5)) ))
El siguiente código proporciona un pipeline de agregación equivalente en la Query API:
[ { $addFields: { reunionYear: { $add: [ { $toInt: "$graduationYear" }, 5 ] } } } ]
Operaciones de fechas
Puedes realizar una operación de fecha en un valor de tipo MqlDate utilizando los métodos descritos en esta sección.
Método | Operador de canalización de agregación |
|---|---|
Supongamos que tiene datos sobre entregas de paquetes y necesita hacer coincidir 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 qué día de la semana es y lo convierte en un número basado en qué día es 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 el pipeline 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 un pipeline de agregación equivalente en la Query API:
[ { $match: { $expr: { $eq: [ { $dayOfWeek: { date: { $dateFromString: { dateString: "$deliveryDate" } }, timezone: "America/New_York" }}, 2 ] } } } ]
Operaciones de documentos
Puede realizar una operación de documento en un valor de tipo MqlDocument utilizando los métodos descritos en esta sección.
Método | Operador de canalización de agregación |
|---|---|
No hay operador correspondiente | |
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 el pipeline 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 un pipeline de agregación equivalente en la Query API:
[ { $match: { $expr: { $eq: [{ $getField: { input: { $getField: { input: "$$CURRENT", field: "mailing.address"}}, field: "state" }}, "WA" ] }}}]
Operaciones de mapas
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 ID de elementos, a valores.
Método | Operador de canalización 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 |
Supón que tienes una colección de datos de inventario en la que cada documento representa un artículo individual del que eres responsable de suministrar. Cada documento contiene un campo que es un mapa de todos tus almacenes y la cantidad de copias que actualmente tienen en su inventario del artículo. Se quiere determinar el número total de copias de ítems que se tiene en todos los almacenes. Un documento en esta colección podría parecerse 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 un arreglo. El método sum() calcula el valor total de los ítems en función de los valores del arreglo recuperados con el método getValue(). Este ejemplo almacena el resultado como el nuevo campo totalInventory usando el método project().
El siguiente código muestra el pipeline 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 un pipeline de agregación equivalente en la Query API:
[ { $project: { totalInventory: { $sum: { $getField: { $objectToArray: "$warehouses" }, } } } } ]
Operaciones de cadenas
Puedes realizar una operación de string en un valor de tipo MqlString utilizando los métodos descritos en esta sección.
Método | Operador de canalización de agregació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 usando el método project().
El siguiente código muestra el pipeline 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 un pipeline de agregación equivalente en la Query API:
[ { $project: { username: { $toLower: { $concat: ["$lastName", "$employeeID"] } } } } ]
Operaciones de comprobación de tipos
Puedes realizar una operación de comprobació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 por defecto que coincide con el tipo especificado por el método. Si el valor comprobado coincide con el tipo de método, se devuelve el valor comprobado. De lo contrario, se devuelve el valor por defecto proporcionado. Si deseas programar una lógica de ramificación basada en el tipo de datos, consulta switchOn().
Método | Operador de canalización 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 |
Suponga que tiene una colección de datos de calificaciones. Una versión temprana del esquema de revisiones permitía a los usuarios enviar revisiones negativas sin una calificación en estrellas. Quiere convertir cualquiera de estas revisiones negativas sin calificación con estrellas para que tengan el valor mínimo de una estrella.
El método isNumberOr() devuelve el valor de rating, o un valor de 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 el pipeline 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 un pipeline de agregación equivalente en la Query API:
[ { $project: { numericalRating: { $cond: { if: { $isNumber: "$rating" }, then: "$rating", else: 1 } } } } ]