4 Agregación Pipeline¶
Introducción¶
La agregación pipeline o tuberías de agregación se basa en someter una colección a un conjunto de operaciones o etapas, estas etapas irán convirtiendo y transformando el conjunto de documentos pertenecientes a la colección, hasta obtener un conjunto de documentos con el resultado deseado.
Se le llama tubería ya que cada etapa irá modificando, moldeando y calculando la estructura de los documentos para pasarlos a la etapa que le sigue.
Funciones de agregado¶
Al igual que en otra base de datos, MongoDB dispone de funciones matemáticas y de cadenas para utilizaras en las consultas.
Funciones Aritméticas¶
Función | Descripción |
---|---|
$abs | Valor absoluto de un número |
$add | Añade números a una cantidad o a una fecha, en este caso suma milisegundos. |
$ceil | Devuelve el entero menor, mayor o igual que el número especificado. |
$divide | Devuelve el resultado de dividir el primer número por el segundo |
floor | Devuelve el entero mayor, menor o igual que el número especificado. |
$mod | Devuelve el resto de dividir el primero número por el segundo |
$multiply | Multiplica varios números, acepta varios argumentos. |
$pow | Eleva un número a la potencia especificada |
$sqrt | Calcula la raíz cuadrada |
$subtract | Devuelve el resultado de restar el primer número menos el segundo. Si los dos valores son fechas devuelve la diferencia en milisegundos. |
$trunc | Trunca un número |
Funciones de Cadenas¶
Función | Descripción |
---|---|
$concat | Concatena varias cadenas |
$substr | Devuelve una subcadena, a partir de una posición indicada hasta una longitud especificada. |
$toLower | Convierte una cadena a minúsculas |
$toUpper | Convierte una cadena mayúsculas |
$strcasecmp | Compara cadenas y devuelve 0 si ambos son iguales, 1 si la primera es mayor que la segunda, o -1 si la primera es menor que la segunda |
Funciones de Grupo¶
Función | Descripción |
---|---|
sum | Devuelve la sma de los valores numéricos, ignorando los no numéricos. |
$avg | Devuelve la media de los valores numéricos e ignora los no numéricos |
$first | Devuelve el primer valor del grupo |
$last | Devuelve el último valor |
$max | Devuelve el valor máximo |
$min | Devuelve el valor mínimo |
Funciones de Fecha¶
Función | Descripción |
---|---|
$dayOfYear | Devuelve el día del año |
$dayOfMonth | Devuelve el día del mes. |
$dayOfWeek | Devuelve el día de la semana |
$year | Devuelve el año |
$month | Devuelve el número del mes |
$hour | Devuelve la hora |
$minute | Devuelve los minutos |
$second | Devuelve los segundos |
$dateToString | Devuelve la fecha en String |
Uso de estas funciones¶
Estas funciones se utilizan en las operaciones de agregación, o consultas de agregación, que lo que hacen es procesar los registros y obtener nuevos resultados, calculados o transformados.
La agregación opera con grupos de valores de múltiples documentos y se puede realizar una variedad de operaciones sobre los datos agrupados para devolver un solo resultado. El objetivo es presentar datos calculados, formateados y/o filtrados de manera diferente a como se encuentra en los documentos.
MongoDB ofrece tres formas de realizar la agregación: la agregación pipeline, la función de map-reduce y la agregación de propósito único.
Etapas Pipeline¶
Las etapas son las siguientes:
Etapa | Descripción | Multiplicidad |
---|---|---|
$match | Filtra los resultado. Permite filtrar los resultados de los documentos que cumplen cierto criterios. Se puede filtrar antes o después de agregar resultados. | n:1 |
$group | Agrupación. Permite agrupar distintos documentos según compartan el valor de uno o varios de sus atributos, y realizar operaciones sobre los elementos de cada uno de los grupos. Se utilizan las funciones de grupo | n:1 |
$sort | Ordenación de los documentos | 1:1 |
$skip | Salta N documentos | n:1 |
$limit | Elige N elementos para el resultado | n:1 |
$unwind | Normaliza arrays | 1:n |
$out | Envía el resultado a una salida, se almacena en la BD como una nueva colección | 1:1 |
La multiplicidad se refiere a cuántos documentos obtenemos como resultado después de aplicar la etapa. De esta forma 1:1 se aplica a un documento y se obtiene otro, n:1 se aplica a n documentos y se obtiene 1, y 1:n se aplica a un documentos y se obtienen n.
Formato pipeline¶
El formato de pipe line es la siguiente:
Por ejemplo:
Tenemos la colección articulos, cada registro está formado por los campos: código, denominación, pvp, categoría, uv, y stock.
Vamos a obtener los articulos de los artículos y la categoría convertida a mayúsculas. Se utiliza la etapa $project pues cambiamos el aspecto del documento.
¡Importante!
Para referirnos a los campos del documento los ponemos entre comillas y con el prefijo $.
db.articulos.aggregate([
{
$project:
{
denominacion: {$toUpper: "$denominacion"}
cetegoria: {$toUpper: "$categoria"}
}
}
])
Si dicha salida se desea añadir a la base de datos, se le añade la etapa out:
db.articulos.aggregate([
{
$project:
{
denominacion: {$toUpper: "$denominacion"}
cetegoria: {$toUpper: "$categoria"}
}
},
{ $out: "salidanueva"}
])
Vamos a obtener la denominación en mayúsculas, el importe de las ventas que serán las uv * pvp, y el stock actual que será stock - uv.
Las etapas a usar en este caso son: project, multiply, substract.
db.articulos.aggregate([
{
$project: {
articulo: {$toUpper: "$denominacion"},
importe: { $multiply: ["$pvp", "$uv"]},
stockactual: {$subtract: ["$stock", "$uv"]}
}
}
])
Condiciones de agregación¶
Podemos añadir las siguientes condiciones a las consultas de agregación
Name | Descripción |
---|---|
$cond | Este operador evalúa una expresión y dependiendo del resultado, devuelve el valor de una de las otras dos expresiones. Recibe tres expresiones en una lista ordenada: {$cond: [ \ |
$ifNull | Devuelve o bien el resultado no nulo de la primera expresión o el resultado de la segunda expresión si la primera expresión da como resultado un resultado nulo. El resultado de la segunda expresión puede ser nulo. { $ifNull: [ \ |
Ejemplo: A la consulta anterior, vamos a preguntar si el stock actual es negativo, asignaremos a un campo nuevo llamado reponer true si es menor que 0 y false si no lo es:
db.articulos.aggregate([
{
$project: {
articulo: {$toUpper: "$denominacion"},
importe: { $multiply: ["$pvp", "$uv"]},
stockactual: {$subtract: ["$stock", "$uv"]},
reponer: {
$cond: [
{$lte: [{$substract: ["stock", "uv"]}, 0]}, true, false
]
}
}}
])
En la siguiente consulta, obtenemos por cada categoría el número de artículos, el total unidades vendidas de artículos, y el total importe, la suma de los pvp*unidades. Se utiliza la etapa $group
, cuando se usa esta etapa se debe añadir el identificador de objeto _id, en este caso como agrupamos por categoría lo indicamos en el _id. Para contar artículos se utiliza la función $sum
db.articulos.aggregate([
{
$group:{
_id: "$categoria",
contador: {$sum: 1},
sumaunidades: {$sum: "$uv"},
totalimporte: {$sum: {$multiply: ["$pvp", "$uv"]}}
}
}
])
En la siguiente consulta obtenemos el número de documentos de la categoría Deportes, el total de unidades vendidas de sus artículos, el total del importe y la media de unidades vendidas. Se utilizan las etapas $match
para seleccionar la categoría, y luego $group
para obtener resultados agrupados
db.articulos.aggregate([
{$match: {categoria: "Deportes"}},
{$group: {
_id: "Deportes",
contador: {$sum: 1},
sumaunidades: { $sum: "$uv"},
media: { $avg: "$uv"},
totalimporte: { $sum: { $multiply: ["$pvp", "$uv"]}}
}}
])
Obtenemos, ahora el precio más caro:
¿Y sí, queremos obtener el artículo con el precio más caro?
En primer lugar, debemos obtener los datos pvp y denominación, de todos los articulos ordedanos descendentemente por el precio y denominación. ($sort)
Después, el resultado obtenido se agrupa para luego coger el primero con la función $first
db.articulos.aggregate([
{ $sort: { pvp: -1, denominacion: -1}}
{
$group:{
_id:null,
mascaro: {$first: "$denominacion"}
precio: {$first: "$pvp"}
}
}
])
Ahora, queremos obtener la suma del importe de los artículos cuya denominación empieza por M o P.
Primero, debemos obtener el primer carácter de todos los artículos utilizando la función $substr:
{
$project: {
primercarac: { $substr: ["$denominacion", 0, 1]},
impor: { $multiply: ["$pvp", "$uv"]}
}
}
Después seleccionamos, de los datos obtenidos, lo que tienen en primercarac P o M:
Finalmente, se agrupa el resultado y se suma los importes:
La consulta completa sería:
db.articulos.aggregate([
{
$project: {
primercarac: { $substr: ["$denominacion", 0, 1]},
impor: { $multiply: ["$pvp", "$uv"]}
}
},
{$match: {"primercarac": {$in: ["M", "P"]}}},
{
$group:{
_id: 1,
totalimporte: { $sum: "$impor"}
}
}
])
En la siguiente consulta, obtenemos por cada categoría el artículo con el precio más caro. Para ello primero ordenamos descendentemente por pcategoría, pvp y denominación, utilizando la etapa $sort
. Y el resultado obtenido se agrupa con $group
para luego obtener el primero de cada categoría con la función $first
db.articulos.aggregate([
{$sort: {categoria: -1, pvp: -1, denominacion: -1}},
{
$group:{
_id: "$categoria",
mascaro: { $first: "$denominacion"},
precio: {$first: "$pvp"}
}
}
])
Utilización de Arrays, Campos compuestos y agregados¶
Las siguientes funciones sirve trabajar con arrays:
Nombre | Descripción |
---|---|
$arrayElemAt | Devuelve el elemento especificado en el índice |
$concatArrays | Devuelve un array concatenado en una cadena |
$filter | Selecciona elementos de un array y devuelve otro array con esos elementos. |
$isArray | Determina si el operando es un array o no. Devuelve true o false |
$size | Devuelve el número de elementos del array |
$slice | Devuelve un sub-set de elementos del array, especificando el número |
Vamos a usar ahora la colección Trabajadores. Vamos a insertar un nuevo trabajador
db.trabajadores.insert(
{
nombre: {nom: "Alicia", ape1: "Ramos", ape2: "Martín"},
direccion: {poblacion: "Madrid", calle: {"Avda Toledo 10", }},
salario: 1200,
oficios: ["Profesora", "Analista"],
primas: [20,30,40],
edad: 50
}
)
Como podemos observar un empleado, tiene dos campos compuestos, nombre y dirección, y además dos listas: oficios y primas.
Vamos a consultar la población, el nombre descompuesto en nombre, ape1 y ape2, el primer oficio, el segundo y el último. En caso de no tenerlas no devuelve nada. Además lo vamos a ordenar de forma ascendente
Importante
Para acceder a los campos compuestos navegamos como si fuese un objeto. Por ejemplo, si quiero acceder al campo nom del nombre o al campo poblacion de la dirección, sería: nombre.nom o direccion.poblacion
db.trabajadores.aggregate([
{$sort: {direccion.poblacion: 1}},
{$project:
{
poblacion: "$direccion.poblacion",
nombre: "$nombre.nom",
ape1: "$nombre.ape1",
ape2: "$nombre.ape2",
oficio1: {$arrayElementAt: ["$oficios", 0]},
oficio2: {$arrayElementAt: ["$oficios", 1]},
oficio3: {$arrayElementAt: ["$oficios", -1]}
}
}
])
Ahora, obtenemos los elementos que tienen los arrays de los trabajadores, y los arrays concatenados. Se utiliza la función $ifNull para comprobar que los arrays existan en los trabajadores, y evitar errores de salida. Se pregunta si es null, si lo es devuelve el array vacio.
db.trabajadores.aggregate([
{
$project:
{
nombre: "$nombre.nom",
numerooficios: {$size: {"$ifNull": ["$oficios", []]}},
numeroprimas: { $size: { "$ifNull": ["$primas", []]}},
oficiosconcatenados: {$concatArrays: ["$oficios", "$primas"]}
}
}
])
Por último, devolvemos el número de trabajadores y la media de edad de los trabajadores que han tenido el oficio de Analista
db.trabajadores.aggregate([
{$match: {oficios: "Analista"}},
{$group: {
_id: "analista",
contador: { $sum:1 },
media: {$avg: "$edad"}
}}
])
Ejercicio
Utilizando la colección trabajadores realiza las siguientes consultas:
- Visualiza la edad media, la media del salario y el número de trabajadores que hayan tenido una prima de 30 o de 80.
- Visualiza por población el número de trabajadores, el salario medio y el máximo salario.
- Visualiza el nombre, ape1, ape2, del empleado que tiene máximo salario
- A partir de la consulta anterior, obtén ahora el nombre, ape1, ape2 y salario del empleado que tiene máximo salario por cada población.