Pipelines de agregación en MongoDB

Pipelines de agregación de MongoDB: Cómo consultar la base de datos de Mongo

¿Porqué utilizar MongoDB?

MongoDB es una base de datos de open source NOSQL orientada a documentos, lo que significa que los datos no tienen que seguir necesariamente un esquema determinado. Esto hace que MongoDB sea un candidato ideal como base de datos para cargas de trabajo de big data, ya que garantiza un mejor rendimiento y se deshace parcialmente de las preocupaciones creadas por los esquemas forzados o las costosas operaciones ALTER TABLE.
Entonces, ¿cómo consultamos MongoDB?

Dataset utilizado

MongoDB almacena los documentos en un formato llamado BSON (o JSON binario), cada documento es autodescriptivo e incluye su longitud y tipos de datos. La unidad mínima de datos es un pequeño documento de par clave-valor. A lo largo de este artículo, trabajaremos con el siguiente conjunto de datos:

db.restaurantOpinions.insertMany([
   {"email": "john@gmail.com", "responseDate": new Date("2020-03-04"), questions: [{"questionId": "6a4c", "answer": "Lasagne"}, {"question": "94fa", "answer": 5}]},
   {"email": "john@gmail.com", "responseDate": new Date("2020-06-09"), questions: [{"questionId": "6a4c", "answer": "Pizza"}, {"question": "94fa", "answer": 3}]},
   {"email": "jessie@gmail.com", "responseDate": new Date("2021-02-04"), questions: [{"questionId": "6a4c", "answer": "Chicken Salad"}, {"question": "94fa", "answer": 4}]},
   {"email": "jessie@gmail.com", "responseDate": new Date("2021-02-20"), questions: [{"questionId": "6a4c", "answer": "Chicken Salad"}, {"question": "94fa", "answer": 5}]},
   {"email": "edward@hotmail.com", "responseDate": new Date("2020-12-31"), questions: [{"questionId": "6a4c", "answer": "Roasted pork"}, {"question": "94fa", "answer": 5}]},
   {"email": "emily@yahoo.com", "responseDate": new Date("2020-12-31"), questions: [{"questionId": "6a4c", "answer": "Roasted pork"}, {"question": "94fa", "answer": 5}]}
])

Como podemos ver, las preguntas tienen un id que indica más datos subyacentes. En este caso, los ids tienen los siguientes significados:

  • 6a4c – ¿Cuál es su comida favorita?
  • 94fa – ¿Qué probabilidad tendría de recomendar este restaurante?

Conceptos básicos de los pipelines de agregación

El motor de consulta de MongoDB permite ejecutar pipelines de agregación bastante complejas directamente en la base de datos. Hay varias etapas que se pueden definir, pero antes de sumergirnos en las pipelines más complejas, vamos a echar un vistazo a algunas etapas sencillas para sentirnos cómodos con lo básico:

Supongamos que queremos conocer cien respuestas a la pregunta sobre la comida favorita de nuestro cliente más fiel; Juan, así como la fecha en la que dijo que eran sus favoritos. Podríamos escribir la siguiente consulta:

db.restaurantOpinions.aggregate([
   { $match: { email: "john@gmail.com" } },
   {
       $project: {
           "favoriteFood": { "$filter": {
               "input": "$questions",
               "as": "question",
               "cond": { "$eq": ["$$question.questionId", "6a4c"] }
           } },
           _id: false,
           responseDate: true
       }
   },
   { $limit: 100 }
]);

En esta consulta ocurre lo siguiente:

  • $match filtra el número de resultados de los totales a los que tienen el correo electrónico de Juan. Se recomienda colocar los filtros lo más temprano posible en el pipeline, ya que reduce el número de filas a procesar. Si se coloca al principio, utilizará el índice de la base de datos como si fuera una find query.
  • $project creará un nuevo campo llamado favoriteFoods que sólo contendrá la pregunta con id «6a4c», que representa la comida favorita. También incluirá el responseDate en el resultado final y excluirá el id generado por MongoDB.
  • $limit indicará que sólo queremos conocer un centenar (por cualquier criterio, ya que no está ordenado) de todos los resultados.

Dando los siguientes resultados:

favoriteFoodresponseDate
[{"questionId": "6a4c", "answer": "Lasagne"}]2020-03-04T00:00:00.000Z
[{"questionId": "6a4c", "answer": "Pizza"}]2020-06-09T00:00:00.000Z

Un ejemplo más complejo

Supongamos ahora que queremos averiguar la probabilidad de que cada cliente recomiende, por término medio, este restaurante. Estos datos forman parte de nuestras preguntas, por lo que tendremos que crear una fila para cada pregunta para poder trabajar con ellos.
Podríamos escribir la siguiente consulta:

db.restaurantOpinions.aggregate([
  { $unwind: "$questions" },
  { $match: { "questions.questionId": "94fa" } },
  { $group: {
      _id: "$email",
      recomendationAvg: { $avg: "$questions.answer" }
  }},
  {
      $project: {
          email: "$_id",
          _id: false,
          recomendationAvg: true,
      }
  },
  { $sort: { recomendationAvg : 1 } }
]);

Como podemos ver, sigue una estructura similar a la del ejemplo anterior, sin embargo, $unwind está permitiendo que el resto de las etapas funcionen sin un array sino una fila para cada una de las preguntas. Como antes, vamos a describir la consulta:

  • $unwind creará una fila por cada elemento encontrado en el array de preguntas.
  • $match filtra todas las preguntas, por lo que sólo se mantienen las que tienen id 94fa ya que las siguientes etapas van a trabajar con las respuestas a esas preguntas.
  • $group crea un grupo para cada correo electrónico y calcula el valor medio de las respuestas en ese grupo.
  • $project devolverá el nombre de la columna _id a email, ya que la agrupación devolvió un campo _id y un valor recommendationAvg.
  • $sort tomará todos los resultados y los ordenará por recommendationAvg basándose en un criterio ascendente.

La consulta dará los siguientes resultados:

emailrecomendationAvg
john@gmail.com4
jessie@gmail.com4.5
emily@yahoo.com5
edward@hotmail.com5

Conclusiones

Hemos cubierto algunas etapas básicas y apuntado a otras más complejas, sin embargo, hay muchas más etapas y operadores proporcionados. $lookup merece una mención especial, ya que permite que el pipeline realice un join con otra colección mientras se procesa.

Los pipelines de agregación de MongoDB son potentes y el método preferido para agregar datos. Con la optimización incorporada, los pipelines facilitan la computación de los resultados y sólo necesitan que el usuario sea consciente de unas pocas optimizaciones claramente mencionadas en su documentación.

Si te ha parecido útil este post, te recomendamos visitar la categoría Data Analytics de nuestro blog y a compartirlo con tus contactos para que ellos también puedan leerlo y opinar. ¡Nos vemos en redes!

Víctor Prats
Víctor Prats
Artículos: 8