Creación de tuberías de agregación en MongoDB con $match, $group y $lookup
Aprenda cómo las etapas de agregación de MongoDB filtran, agrupan, remodelan, unen y ordenan documentos, y cómo enlazarlas en una tubería que responde a preguntas reales de informes.
Una consulta simple con find() es adecuada para obtener documentos que cumplan una condición, pero no puede calcular los ingresos totales por cliente, asociar detalles de usuarios a los pedidos ni devolver un informe ordenado. Para ello, MongoDB ofrece el marco de trabajo de agregación. Esta guía explica las cinco etapas que se utilizarán con mayor frecuencia, los pequeños problemas en cada una y cómo combinarlas en un único pipeline que genere un resultado limpio y listo para usar.
Cómo funciona un pipeline de agregación
Una agregación es una lista ordenada de etapas. Los documentos entran en la primera etapa, cada etapa transforma el flujo de datos de una manera específica, y lo que emite se convierte en la entrada de la siguiente etapa. Pensar en ello como una línea de ensamblaje de datos facilita comprender el orden de las etapas.
El pipeline sencillo que se muestra a continuación mantiene solo los pedidos completados, suma la cantidad por cada cliente y muestra primero a los que más gastan.
db.orders.aggregate([
{ $match: { status: "completed" } },
{ $group: { _id: "$customerId", total: { $sum: "$amount" } } },
{ $sort: { total: -1 } }
]);
Las secciones siguientes analizan cada etapa por separado.
Filtrado de documentos con $match
$match acepta el mismo tipo de filtro que se pasaría a find(). En este ejemplo, solo los documentos cuyo status sea igual a "completed" pasan a la siguiente etapa.
{
$match: {
status: "completed"
}
}
Coloque $match lo antes posible. Cada documento que se elimina al principio significa un documento menos que las etapas posteriores deben procesar, y un $match al inicio de una tubería puede utilizar índices en la colección, mientras que las etapas posteriores operan sobre resultados intermedios con los que los índices no pueden ayudar.
Grupación y cálculo de totales con $group
$group recopila los documentos que comparten una clave y calcula valores para cada grupo. El ejemplo genera un documento por cliente con el monto total del pedido.
{
$group: {
_id: "$customerId",
totalSales: {
$sum: "$amount"
}
}
}
Hay dos partes que realizan el trabajo:
_iddefine la clave de agrupación; en este caso es el valor del campocustomerIdde cada documento (el prefijo$significa “leer este campo”).$sumes un acumulador que suma el valor deamountde cada documento del grupo.
Otros acumuladores siguen el mismo patrón, incluyendo $avg, $min, $max y $count. Tenga en cuenta que la salida de $group contiene únicamente _id y los campos que haya calculado; todos los demás campos de los documentos originales desaparecen.
Selección y reorganización de campos con $project
$project determina qué campos aparecen en el resultado y puede crear nuevos. Al establecer un campo en 1 se incluye, mientras que _id: 0 oculta explícitamente el identificador, que de lo contrario se incluye por defecto.
{
$project: {
customerId: 1,
totalSales: 1,
_id: 0
}
}
También puede calcular valores. Aquí $multiply genera un campo totalWithTax aplicando un factor de impuesto del 18% a totalSales.
{
$project: {
customerId: 1,
totalWithTax: {
$multiply: ["$totalSales", 1.18]
}
}
}
Un detalle importante cuando estos fragmentos siguen una etapa $group es que el identificador del cliente se encuentra en _id, no en customerId; por lo tanto, customerId: 1 no mostraría ningún resultado. El pipeline combinado al final soluciona esto escribiendo customerId: "$_id", lo que renombra el campo. Considere $project como la etapa que da forma a la respuesta final que devuelve su API.
Unir otra colección con $lookup
A menudo, los documentos hacen referencia a datos almacenados en otro lugar. Con una colección orders y otra colección users, $lookup obtiene el usuario correspondiente para cada pedido.
{
$lookup: {
from: "users",
localField: "customerId",
foreignField: "_id",
as: "customer"
}
}
Para cada pedido, MongoDB compara el customerId del pedido con el _id de la colección users y escribe todos los resultados coincidentes en un nuevo campo de array llamado customer. Este es el equivalente más cercano a una unión en SQL. El resultado siempre es un array, incluso cuando solo hay un usuario que coincide, por lo que es común utilizar $unwind después de la búsqueda o leer el primer elemento. Además, asegúrese de que ambos campos tengan el mismo tipo: un customerId de tipo cadena no coincidirá con un ObjectId en _id.
Ordenar los resultados con $sort
$sort ordena los documentos según uno o más campos. Un valor de 1 realiza un orden ascendente y -1 uno descendente, por lo que en el ejemplo los clientes se clasifican desde el mayor totalSales hasta el más bajo.
{
$sort: {
totalSales: -1
}
}
Combinar las etapas en un único informe
El verdadero valor se manifiesta cuando las etapas se encadenan. La secuencia a continuación convierte los pedidos en bruto en una lista ordenada de clientes y sus ventas totales:
db.orders.aggregate([
{
$match: {
status: "completed"
}
},
{
$group: {
_id: "$customerId",
totalSales: {
$sum: "$amount"
}
}
},
{
$sort: {
totalSales: -1
}
},
{
$project: {
customerId: "$_id",
totalSales: 1,
_id: 0
}
}
]);
Paso a paso, hace lo siguiente:
- Mantiene solo los pedidos completados con
$match. - Los agrupa por cliente con
$group. - Sume las cantidades de los pedidos de cada cliente en
totalSales. - Ordena a los clientes según ese total, del más alto al más bajo.
- Utiliza
$projectpara renombrar_idacustomerIdy eliminar_idde la respuesta.
Para incluir nombres o correos electrónicos, podría agregar un $lookup dirigido a users. Al colocarlo después de $group y después de cualquier $limit, la unión se ejecuta una vez por cliente en lugar de una vez por pedido.
Puntos clave
Una forma compacta de recordar las etapas principales:
$matchpara filtrar.$grouppara agrupar y calcular.$projectpara seleccionar y reorganizar.$lookuppara unir colecciones.$sortpara ordenar los resultados.
Juntos abarcan la mayor parte de lo que necesitan los informes, paneles de control, puntos finales de análisis y reglas empresariales una vez que se va más allá de las operaciones básicas CRUD. El orden es importante: filtre temprano, agrupe una vez, realice las uniones tan tarde como lo permitan los datos y da forma a la salida al final. En entrevistas y revisiones de diseño, poder justificar por qué una etapa se encuentra en un lugar determinado es más convincente que simplemente enumerar cuál es su función.
Lecturas relacionadas
- Prevenir actualizaciones perdidas en Node.js y MongoDB bajo escrituras concurrentes — Aprenda cómo las actualizaciones condicionales atómicas, el bloqueo optimista basado en versiones, las respuestas 409 y las transacciones impiden que las escrituras concurrentes en MongoDB descarten datos silenciosamente.