El framework de agregación
Introducción
MongoDB proporciona a los usuarios una potente herramienta para analizar y procesar la información: el canal o pipeline de agregación (también conocida como marco de trabajo agregación o framework de agregación).
¿Cómo funciona?
Para explicar cómo funciona el canal de agregación, MongoDB utiliza la metáfora de una cinta transportadora en una fábrica. Los documentos se encuentran en esta cinta transportadora y, a medida que se procesan, se les aplica un cierto número de procesos: estos procesos se denominan pasos (stages).
Así pues, un pipeline consta generalmente de uno o varios pasos que utilizan operadores denominados operadores de agregación, algunos de cuyos nombres ya hemos visto en capítulos anteriores. El método utilizado para ejecutar un pipeline sobre una colección se denomina aggregate y su sintaxis es:
db.collection.aggregate(< canal >, < opciones >)
El parámetro pipeline es una matriz de pasos, mientras que options es un documento. Algunas de las opciones notables en este método son:
-
collation, que se utiliza para asignar una intercalación a la operación de agregación.
-
bypassDocumentValidation, que solo funciona con el operador $out y permite saltarse la validación del documento.
-
allowDiskUse permite que las operaciones de escritura se desborden sobre el disco.
Más adelante veremos que, aunque la utilidad de proceder de este modo es prácticamente nula, se puede invocar el aggregate sin ningún argumento.
Un paso puede identificarse por el operador que utiliza, que es siempre la clave del documento que lo representa. Como...
Pasos para la agregación
Para trabajar en la agregación, vamos a borrar la colección anterior de personas y crear una nueva:
db.personas.drop()
db.personas.insertMany(
[
{
"apellido": "Dupont",
"nombre": "Catherine",
"intereses": ["cocina"],
"edad": 66
}, {
"apellido": "Duport",
"nombre": "Eric",
"intereses": ["cocina", "petanca"],
"edad": 57
}, {
"apellido": "Duport",
"nombre": "Arlette",
"intereses": ["jardinería"],
"edad": 80
}, {
"apellido": "Lejeune",
"nombre": "Jean",
"intereses": ["jardinería"],
"edad": 75
}, {
"apellido": "Lejeune",
"nombre": "Mariette",
"intereses": ["jardinería", "bridge"],
"edad": 66
}]
)
1. Filtrar usando $match
El paso $match es crucial para conseguir pipelines de alto rendimiento con tiempos de ejecución cortos. Debe situarse lo más arriba posible en la pipeline, ya que actúa como filtro y reduce el número de documentos que deben procesarse más adelante en la cadena. Lo ideal sería que fuera el primero.
La sintaxis de este paso de comparación es sencilla: le pasamos un documento que contiene todas las condiciones de nuestra consulta:
{ $match: { < query > } }
Empecemos escribiendo una canalización o pipeline de un solo paso que utiliza el operador $match: su propósito es filtrar en el documento a personas en sentido ascendente, reteniendo solo a aquellas...
Pasos cursor-like
Estos cuatro pasos del pipeline de agregación llevan el nombre de los métodos encontrados cuando se trabaja con cursores, en inglés «cursor-like», razón por la que se así se denominan.
1. Limitar resultados usando $limit
He aquí una consulta que limita los resultados devueltos en un cursor a tres documentos. No se especifica ningún criterio de búsqueda y solo se utilizan los campos apellido y nombre:
db.personas.find({}, {"_id": 0, "apellido": 1, "nombre": 1}).limit(3)
Se obtienen los siguientes documentos:
{ "apellido" : "Durand", "nombre" : "René" }
{ "apellido" : "Durand", "nombre" : "Gisèle" }
{ "apellido" : "Dupont", "nombre" : "Gaston" }
Como no se han ordenado explícitamente, aparecen en el orden natural en que se insertaron en la colección.
Se trata de la misma consulta traducida a un formato que puede utilizar el framework de agregación:
pipeline = [{
$project: {
"_id": 0,
"apellido": 1,
"nombre": 1
}
},
{
$limit: 3
}]
db.personas.aggregate(pipeline)
2. Contar usando $count
Hagamos una petición muy sencilla que nos devolverá un cursor sobre el que aplicaremos el método...
Operadores de pipeline de agregación
1. Evaluar una expresión usando $cond
Este operador actúa como lo haría una rama condicional de tipo if en cualquier lenguaje de programación: evalúa una expresión booleana y luego efectúa acciones si es verdadera o falsa. Su forma simplificada es:
{ $cond: [ < expresión >, < si es verdadera >, < si es falsa > ] }
Esta sintaxis condensada es bien conocida por los programadores de C++, PHP y Java como operador ternario.
Volvamos a la colección compras y supongamos que se ofrece un vale de descuento a todos los clientes con las siguientes condiciones: quien haya realizado al menos 300 euros en compras recibirá un vale con un valor nominal de 10 euros, y quien no lo haya hecho solo recibirá un vale con un valor nominal de 5 euros. Aquí está el pipeline correspondiente:
db.compras.aggregate([
$addFields: {
"total_compras": { $sum: "$compras" }
}
},
{
$project: {
"_id": 0,
"apellido": 1,
"nombre": 1,
"bono_descuento": {$cond: [{$gte: ["$total_compras", 300]}, 10, 5]}
}
}
])
2. Recorrer y transformar elementos de una tabla usando $map
El operador $map se utiliza para recorrer los diferentes elementos de una tabla aplicándoles una expresión. Devuelve una matriz con los cambios realizados. A continuación, se muestra su firma:
{
$map: {
"input": < expresión que genera una tabla >,
"as": < nombre dado a la variable que representa cada elemento
de la tabla >,
"in": < expresión aplicada a cada elemento >.
}
}
Solo el campo as es opcional. Si no se especifica, tendrá que referirse al elemento actual con el nombre this. Imaginemos que queremos aplicar un vale de descuento de 5 euros a cada producto comprado por cada cliente:
db.compras.aggregate([{...
Buscar por facetas
Este tipo de búsqueda se utiliza cuando se navega por el sitio web de un comerciante y se buscan productos en un catálogo aplicando filtros sobre las propiedades, llamadas facetas (del inglés facet), que contienen. Estas propiedades pueden ser una talla, un color, un precio, una categoría, etc. Por ejemplo, he aquí la búsqueda por facetas en el sitio Discogs, un mercado especializado en la venta en línea de productos musicales (discos, CD, casetes y muchos otros formatos).

Las facetas se enumeran a la izquierda de la imagen y están predefinidas aquí, lo que nos permite buscar entre las 46,993 referencias que el vendedor seleccionado aquí tiene en su catálogo. Para cada uno de ellos, tenemos algunos de los valores tomados con un recuento.
He aquí un ejemplo de búsqueda que combina varios filtros aplicados a algunas de las facetas que pone a nuestra disposición el sitio Discogs. Restringen considerablemente el tamaño del conjunto de resultados, ya que ahora tenemos 3 productos en el campo que nos interesa, a saber, discos de vinilo de más de 40 dólares de grupos que tocan rock psicodélico y progresivo enviados desde Holanda.

Al hacer clic en un valor de determinadas facetas, se actualizan otras, lo que permite al usuario profundizar (drill-down en inglés) en el catálogo. Seleccionar el género «Rock», por ejemplo, actualizará los valores de la faceta «Estilo», que solo conservará aquellos presentes en el género elegido:

Del mismo modo, hacer clic en el estilo «Heavy Metal» repercutirá en los valores de la propia faceta «Estilo», ya que ahora solo contendrá los subestilos del estilo seleccionado:

Echemos otro vistazo a la colección instrumentos y simulemos estas categorías con la cuenta asociada. Una simulación de este tipo puede llevarse a cabo utilizando un pipeline bastante simple:
db.instrumentos.aggregate([{
$group: {
"_id": "$tipo", "count": { $sum: 1}
}
}, {
$project: {"_id": 0, "categoría": "$_id", "cuenta":...