Sergio Brandon De Lucio Chavero
Waldir Zapata Garcia
Cuando trabajas con agregaciones en MongoDB, llega un momento en que el área comercial o cualquier consumidor de tus datos te pide algo muy puntual: que el resultado salga con nombres específicos, sin campos innecesarios y listo para usarse. Ahí entra el operador project, una pieza clave del aggregation pipeline que te permite moldear la salida exactamente como te la están pidiendo.
¿Qué hace el operador project en MongoDB?
El operador project actúa como un escultor del JSON final. Renombra campos, crea campos calculados a partir de otros y elimina los que no quieres mostrar.
¿Qué es el operador project en MongoDB? Es una etapa del pipeline de agregación que te deja modelar la salida: cambiar nombres de campos, computar campos nuevos a partir de otros y excluir los que no necesitas en el resultado final.
Esto importa porque pocas veces el formato crudo de una agrupación coincide con lo que pide quien va a leer los datos. Con project tienes el control total del formato de salida.
¿Cómo aplicar project después de un group?
Imagina que el departamento comercial te pide dejar de enviarles JSON con nombres técnicos y entregarles algo legible: tipo de propiedad, menor precio y mayor precio. Tu pipeline ya tiene una etapa group con la llave property_type y dos acumuladores, masBajo y masAlto, que guardan el documento con menor y mayor precio respectivamente.
La nueva etapa con project se agrega justo después de group y se construye como una lista de campos donde tú decides el nombre de salida y a qué dato apunta cada uno.
¿Cómo renombrar la llave del group con project?
El resultado de un group siempre guarda la llave de agrupación dentro del campo _id. Si quieres mostrarlo como "tipo de propiedad", referencias $_id dentro del nuevo nombre.
js { $project: { "tipo de propiedad": "$_id", "menor precio": "$masBajo.precio", "mayor precio": "$masAlto.precio", _id: 0 } }
Fíjate en la sintaxis con punto: $masBajo.precio baja un nivel dentro del documento acumulado y trae solo el precio. Lo mismo aplica para $masAlto.precio. Así obtienes valores planos en lugar del documento completo.
¿Cómo ocultar el campo _id en el resultado?
Por defecto, project incluye el _id aunque tú no lo pidas. Para evitar que aparezca duplicado junto a tu nuevo campo "tipo de propiedad", basta con asignarle 0.
¿Cómo elimino el _id en una agregación de MongoDB? Dentro de la etapa
$project, escribe_id: 0. Eso le indica a MongoDB que excluya ese campo del documento de salida.
Con esa línea, tu resultado final muestra solo los tres campos que el área comercial pidió, sin ruido extra.
¿Se puede usar project más de una vez en un pipeline?
Sí, y aquí viene lo interesante. Puedes encadenar varios $project en el mismo pipeline. Una primera etapa transforma los datos, y una segunda toma esa salida ya transformada para volver a darle forma.
Esto sirve cuando necesitas:
$sort o $match con campos ya renombrados.Usar project varias veces te obliga a pensar el pipeline como una cadena de transformaciones, no como un único bloque monolítico.
¿Cuándo conviene usar project en tus agregaciones?
Project se vuelve indispensable cuando el consumidor del dato necesita un contrato específico. Pocos casos típicos:
En el caso del reto, la combinación de $group seguido de $project resuelve dos problemas a la vez: agrupar por tipo de propiedad para sacar el menor y mayor precio, y luego presentar todo con los nombres exactos que pidió el equipo comercial.
Ahora te toca a ti: arma un pipeline donde uses project una, dos o más veces y comparte en los comentarios qué transformaciones hiciste en cada etapa.
Sergio Brandon De Lucio Chavero
Waldir Zapata Garcia
Carlos Orejuela
Ronaldo Jiménez
Carla Antonella Palmieri
Yo tengo este pipeline con dos projects:
[ { '$match': { 'idproducto': { '$nin': [ '', 0, False ] } } }, { '$project': { 'idproducto': 1, 'descripcion': 1, 'idgrupo': 1, 'createdAt': 1, 'updatedAt': 1, 'idEmpresa': 1 } }, { '$lookup': { 'from': 'productosDetalle', 'localField': 'idproducto', 'foreignField': 'idproducto', 'as': 'detallesProducto' } }, { '$lookup': { 'from': 'grupos', 'localField': 'idgrupo', 'foreignField': 'idgrupo', 'as': 'categoria' } }, { '$unwind': { 'path': '$detallesProducto' } }, { '$addFields': { 'name': '$descripcion', 'companyId': '$idEmpresa', 'description': '', 'price': '$detallesProducto.precio', 'code': '', 'category': { '$arrayElemAt': [ '$categoria.descripcion', 0 ] } } }, { '$project': { 'name': 1, 'companyId': 1, 'description': 1, 'price': 1, 'code': 1, 'category': 1, 'createdAt': 1, 'updatedAt': 1 } } ]
pero ahora que se que project puede crear y eliminar, puedo mejorar mi pipeline
El operador $map es mucho más eficiente que el operador $unwind. Junto a este operador, tambien se debe utilizar $mergeobjects. { "$mergeObjects": "$$this" }
Se puede emplear 2 veces $project si tienes una colección de ventas, donde cada documento contiene el importe unitario de un producto y la cantidad asociada. Luego, si deseas solo quedarte con las de un año particular para realizar un $group por localización:
[ { $project: { _id: 0, idSale: "$_id", importeTotal: $expr: {$multiply: ["$importe", "$cantidad"]}, year: {$year: "$date"}, localizacion: 1 } }, { $match: {year: 2025} }, { $group: { _id: "localizacion", totalImport: {$sum: "$importe_total"} } }, { $project: { _id: 0, localizacion: "$_id", totalImport: 1 } } ]
Y si, fue un ejemplo un poco rebuscado.
Lo nuevo respecto al código anterior:
{ $project: { "_id": 0, "Tipo de propiedad": "$_id", "Menor precio": "$masBajo.precio", "Mayor precio": "$masAlto.precio" } } ```{ $project: { "\_id": 0, "Tipo de propiedad": "$\_id", "Menor precio": "$masBajo.precio", "Mayor precio": "$masAlto.precio" } }
Agregue una conversion de decimal a enteros para que los numeros se vean mas simples:
use("sample_airbnb") db.listingsAndReviews.aggregate([ { $match: { "address.country_code": "US" } }, { $sort: { property_type: 1, price: 1, }, }, { $group: { _id: "$property_type", masBarato: { $first: { nombre: "$name", price: "$price", direccion: "$address" } }, masCaro: { $last: { nombre: "$name", price: "$price", direccion: "$address" } } } }, { $project: { "Tipo de propiedad": "$_id", "Menor precio": { $convert: { input: "$masBarato.price", to: "double", onError: 0.0, onNull: 0.0 } }, "Mayor precio": { $convert: { input: "$masCaro.price", to: "double", onError: 0.0, onNull: 0.0 } } } } ])