() translation by (you can also view the original English article)
Listo para continuar aprendiendo acerca de MongoDB, una de las más frescas tecnologías para desarrolladores web? En ésta segunda parte de la serie, nos moveremos de lo básico, a las consultas avanzadas - con operadores condicionales - y MapReduce.
Yendo más allá de los conceptos básicos
Antes, cubrimos los conceptos básicos de cómo empezar a trabajar con mongoDB, absolutamente una de las mejores implementaciones del mercado NoSQL. Vimos cómo instalarlo, crear una base de datos simple y luego realizar operaciones básicas en ella:
- Buscar
- Actualizar
- Eliminar
Además de esto, comenzamos a mirar cómo empezar a interactuar con mongoDB en una forma más poderosa, mediante el uso de selectores. Los selectores dan la capacidad de tener control mucho más fino y a escarbar muy profundamente hasta encontrar los datos que realmente nos interesa.
Hasta ahora está bien para comenzar, pero cuando se desea escribir una aplicación real, es necesario ir mucho más lejos. Bueno, esto sigue siendo una serie de introducción después de todo, pero quiero emocionarlo con las posibilidades de trabajar en un modo orientado a documentos. Quiero entusiasmarlo a tomar esta gran tecnología y apropiarla y utilizarla tan poderosamente como puedas para hacer fantásticas aplicaciones.
Hoy, vamos a ampliar sobre consultas por última vez y aprender dos aspectos clave de mongoDB:
- Consultas Avanzadas
- MapReduce
Consultas Avanzadas
Previamente vimos consultas básicas y fuimos introducidos a los selectores. Ahora vamos a internarnos en consultas más avanzadas, construyendo en el trabajo anterior dos formas principales:
- Operadores Condicionales
- Expresiones Regulares
Cada uno de ellos nos proporciona sucesivamente más control más fino sobre las consultas que podemos escribir y, en consecuencia, la información que podemos extraer de nuestras bases de datos mongoDB.
Operadores Condiconales
Los operadores condicionales son, como su nombre lo indica, operadores para consultas sobre colecciones que refinan las condiciones con las que la consulta debe coincidir cuando se extraen datos desde la base de datos. Hay un número de ellos, pero hoy me voy a centrar en las 9 principales. Estos son:
- $lt – valor debe ser menor que el condicional
- $gt – valor debe ser mayor que la condicional
- $lte – valor debe ser menor o igual que el condicional
- $gte – valor debe ser mayor o igual al condicional
- $in – valor debe estar en un conjunto de condicionales
- $nin – valor NO debe estar en un conjunto de condicionales
- $not – valor debe ser igual a un condicional
Echemos un vistazo a cada uno. Abrir un terminal y prepárate para usar la base de datos original de la primera parte de esta serie (antes de las modificaciones). Para facilitar este tutorial, vamos a hacer una ligera modificación a la base de datos. Vamos a dar a cada documento de nuestra colección un atributo de edad. Para ello, ejecute la siguiente consulta de modificación:
1 |
|
2 |
db.nettuts.update({"_id" : ObjectId("4ef224be0fec2806da6e9b27")}, {"$set" : {"age" : 18 }}); |
3 |
db.nettuts.update({"_id" : ObjectId("4ef224bf0fec2806da6e9b28")}, {"$set" : {"age" : 45 }}); |
4 |
db.nettuts.update({"_id" : ObjectId("4ef224bf0fec2806da6e9b29")}, {"$set" : {"age" : 65 }}); |
5 |
db.nettuts.update({"_id" : ObjectId("4ef224bf0fec2806da6e9b2a")}, {"$set" : {"age" : 43 }}); |
6 |
db.nettuts.update({"_id" : ObjectId("4ef224bf0fec2806da6e9b2b")}, {"$set" : {"age" : 22 }}); |
7 |
db.nettuts.update({"_id" : ObjectId("4ef224bf0fec2806da6e9b2c")}, {"$set" : {"age" : 45 }}); |
8 |
db.nettuts.update({"_id" : ObjectId("4ef224bf0fec2806da6e9b2d")}, {"$set" : {"age" : 33 }}); |
Si todo sale bien, puede ejecutar un 'find all' y obtendrá el siguiente resultado:
1 |
|
2 |
db.nettuts.find(); |
3 |
{ "_id" : ObjectId("4ef224be0fec2806da6e9b27"), "age" : 18, "dob" : "21/04/1978", "first" : "matthew", "gender" : "m", "hair_colour" : "brown", "last" : "setter", "nationality" : "australian", "occupation" : "developer" } |
4 |
{ "_id" : ObjectId("4ef224bf0fec2806da6e9b28"), "age" : 45, "dob" : "26/03/1940", "first" : "james", "gender" : "m", "hair_colour" : "brown", "last" : "caan", "nationality" : "american", "occupation" : "actor" } |
5 |
{ "_id" : ObjectId("4ef224bf0fec2806da6e9b29"), "age" : 65, "dob" : "03/06/1925", "first" : "arnold", "gender" : "m", "hair_colour" : "brown", "last" : "schwarzenegger", "nationality" : "american", "occupation" : "actor" } |
6 |
{ "_id" : ObjectId("4ef224bf0fec2806da6e9b2a"), "age" : 43, "dob" : "21/04/1978", "first" : "tony", "gender" : "m", "hair_colour" : "brown", "last" : "curtis", "nationality" : "american", "occupation" : "developer" } |
7 |
{ "_id" : ObjectId("4ef224bf0fec2806da6e9b2b"), "age" : 22, "dob" : "22/11/1958", "first" : "jamie lee", "gender" : "f", "hair_colour" : "brown", "last" : "curtis", "nationality" : "american", "occupation" : "actor" } |
8 |
{ "_id" : ObjectId("4ef224bf0fec2806da6e9b2c"), "age" : 45, "dob" : "14/03/1933", "first" : "michael", "gender" : "m", "hair_colour" : "brown", "last" : "caine", "nationality" : "english", "occupation" : "actor" } |
9 |
{ "_id" : ObjectId("4ef224bf0fec2806da6e9b2d"), "age" : 33, "dob" : "09/12/1934", "first" : "judi", "gender" : "f", "hair_colour" : "white", "last" : "dench", "nationality" : "english", "occupation" : "actress" } |
$lt/$lte
Ahora vamos a buscar a todos los actores que tienen menos de 40 años. Para ello, ejecute la siguiente consulta:
1 |
|
2 |
db.nettuts.find( { "age" : { "$lt" : 40 } } ); |
Después de ejecutar esa consulta, verá el siguiente resultado:
1 |
|
2 |
{ "_id" : ObjectId("4ef224be0fec2806da6e9b27"), "age" : 18, "dob" : "21/04/1978", "first" : "matthew", "gender" : "m", "hair_colour" : "brown", "last" : "setter", "nationality" : "australian", "occupation" : "developer" } |
3 |
{ "_id" : ObjectId("4ef224bf0fec2806da6e9b2b"), "age" : 22, "dob" : "22/11/1958", "first" : "jamie lee", "gender" : "f", "hair_colour" : "brown", "last" : "curtis", "nationality" : "american", "occupation" : "actor" } |
4 |
{ "_id" : ObjectId("4ef224bf0fec2806da6e9b2d"), "age" : 33, "dob" : "09/12/1934", "first" : "judi", "gender" : "f", "hair_colour" : "white", "last" : "dench", "nationality" : "english", "occupation" : "actress" } |
¿Y los que tienen menos de 40 años? Ejecutar la siguiente consulta para obtener ese resultado:
1 |
|
2 |
db.nettuts.find( { "age" : { "$lte" : 40 } } ); |
Esto devuelve la siguiente lista:
1 |
|
2 |
{ "_id" : ObjectId("4ef224be0fec2806da6e9b27"), "age" : 18, "dob" : "21/04/1978", "first" : "matthew", "gender" : "m", "hair_colour" : "brown", "last" : "setter", "nationality" : "australian", "occupation" : "developer" } |
3 |
{ "_id" : ObjectId("4ef224bf0fec2806da6e9b2b"), "age" : 22, "dob" : "22/11/1958", "first" : "jamie lee", "gender" : "f", "hair_colour" : "brown", "last" : "curtis", "nationality" : "american", "occupation" : "actor" } |
4 |
{ "_id" : ObjectId("4ef224bf0fec2806da6e9b2d"), "age" : 33, "dob" : "09/12/1934", "first" : "judi", "gender" : "f", "hair_colour" : "white", "last" : "dench", "nationality" : "english", "occupation" : "actress" } |
$gt/$gte
Ahora vamos a buscar a todos los actores mayores de 47 años. Ejecutar la siguiente consulta para encontrar esa lista:
1 |
|
2 |
db.nettuts.find( { 'age' : { '$gt' : 47 } } ); |
A continuación obtendrá la siguiente salida:
1 |
|
2 |
{ "_id" : ObjectId("4ef224bf0fec2806da6e9b29"), "age" : 65, "dob" : "03/06/1925", "first" : "arnold", "gender" : "m", "hair_colour" : "brown", "last" : "schwarzenegger", "nationality" : "american", "occupation" : "actor" } |
¿Y como incluir los de 47 años?
1 |
|
2 |
db.nettuts.find( { 'age' : { '$gte' : 47 } } ); |
Como hay sólo una persona de más de 47 años, no cambian los datos devueltos.
$in/$nin
¿Y como encontrar información basada en una lista de criterios? Los primeros han estado ok, pero sin duda, bastante triviales. Ahora vamos a buscar que gente tenemos que sean actores o desarrolladores. Con la siguiente consulta, lo descubriremos (para que sea un poco más fácil de leer, hemos limitado las llaves que devuelven sólo el nombre y el apellido):
1 |
|
2 |
db.nettuts.find( { 'occupation' : { '$in' : [ "actor", "developer" ] } }, { "first" : 1, "last" : 1 } ); |
Esta consulta produce el siguiente resultado:
1 |
|
2 |
{ "_id" : ObjectId("4ef224be0fec2806da6e9b27"), "first" : "matthew", "last" : "setter" } |
3 |
{ "_id" : ObjectId("4ef224bf0fec2806da6e9b28"), "first" : "james", "last" : "caan" } |
4 |
{ "_id" : ObjectId("4ef224bf0fec2806da6e9b29"), "first" : "arnold", "last" : "schwarzenegger" } |
5 |
{ "_id" : ObjectId("4ef224bf0fec2806da6e9b2a"), "first" : "tony", "last" : "curtis" } |
6 |
{ "_id" : ObjectId("4ef224bf0fec2806da6e9b2b"), "first" : "jamie lee", "last" : "curtis" } |
7 |
{ "_id" : ObjectId("4ef224bf0fec2806da6e9b2c"), "first" : "michael", "last" : "caine" } |
Se puede observar que podemos obtener el inverso de esto mediante $ninjust
simplemente.
Vamos a hacerlo un poco más divertido y combinemos algunos de los operadores. Digamos que queremos buscar todas las personas, que son hombres o desarrolladores, y tengan menos de 40 años de edad.
Ahora es un poco de un bocado, pero con los operadores que hemos utilizado hasta ahora - es fácilmente alcanzable. Vamos a trabajar en él y verás. Echa un vistazo a la siguiente consulta:
1 |
|
2 |
db.nettuts.find( { $or : [ { "gender" : "m", "occupation" : "developer" } ], "age" : { "$gt" : 40 } }, { "first" : 1, "last" : 1, "occupation" : 1, "dob" : 1 } ); |
Puedes ver que hemos estipulado que el sexo puede ser masculino o la ocupación puede ser desarrollador en la condición de $or
y agrega una condición and
a la edad que sea superior a 4.
Por ello, se obtienen los siguientes resultados:
1 |
|
2 |
{ "_id" : ObjectId("4ef22e522893ba6797bf8cb6"), "first" : "matthew", "last" : "setter", "dob" : "21/04/1978", "occupation" : "developer" } |
3 |
{ "_id" : ObjectId("4ef22e522893ba6797bf8cb9"), "first" : "tony", "last" : "curtis", "dob" : "21/04/1978", "occupation" : "developer" } |
Expresiones Regulares
Ahora estoy seguro que no vas a estar satisfecho solo con esto. Le prometí más complejidad y funcionalidad avanzada. Así que vamos a llegar a usar algunas expresiones regulares. Digamos que queremos encontrar los usuarios que tienen un primer nombre empezando con 'ma' o 'a' y que los apellidos comienzan con 'se' o 'de'. ¿Cómo hacerlo?
Echa un vistazo a la siguiente consulta usando una expresión regular:
1 |
|
2 |
db.nettuts.find( { "first" : /(ma|to)*/i, "last" : /(se|de)/i } ); |
Dado ello, los resultados serán:
1 |
|
2 |
{ "_id" : ObjectId("4ef22e522893ba6797bf8cb6"), "first" : "matthew", "last" : "setter", "dob" : "21/04/1978", "gender" : "m", "hair_colour" : "brown", "occupation" : "developer", "nationality" : "australian" } |
3 |
{ "_id" : ObjectId("4ef22e532893ba6797bf8cbc"), "first" : "judi", "last" : "dench", "dob" : "09/12/1934", "gender" : "f", "hair_colour" : "white", "occupation" : "actress", "nationality" : "english" } |
Vamos a ver la consulta un poco más de cerca. En primer lugar, estamos realizando una regex en el nombre.
1 |
|
2 |
"first" : /(ma|to)*/i |
//i
indica que estamos realizando una regex no sensitiva de mayúsculas y minúsculas.
(ma|to)*
indica que ella cadena de inicio del nombre debe ser o 'ma' o 'to'.
Si no está familiarizado, la * al final, coincidirá con cualquier carácter. Así que cuando pones todo junto, coinciden los nombres que tienen cualquiera de los dos datos 'ma' o 'a' al principio de ellos. En la expresión regular para el apellido, se puede ver que hemos hecho lo mismo, pero por el apellido.
¿No está seguro? Vamos a probar otro. Y qué si lo combinamos con uno de los operadores condicionales. Supongamos que queremos encontrar todas las personas con el nombre james o jamie que son actrices femeninas americanas. ¿Cómo hacerlo? Bien, vamos a ver cómo lo hacemos a continuación:
1 |
|
2 |
db.nettuts.find( { "first" : /(jam?e*)*/i, "gender" : "f", "occupation" : "actor", "nationality" : "american" } ); |
La expresión regular anterior coincidirá con combinaciones tales como: james, jamie, jamee, etc. El signo de interrogación corresponderá con un carácter, si a-z, A-Z o 0-9. Entonces, como antes, la * coincide con cualquier cosa que viene después de la 'e'. A partir de ahí, estamos utilizando los operadores condicionales como antes para limitar aún más los resultados que vienen detrás. Cabe señalar que como estamos utilizando el operador no sensitivo de mayúsculas y minúsculas, |, la consulta no utiliza un índice. Pero para efectos de este ejemplo, está bien.
La salida de la consulta anterior es:
1 |
|
2 |
{ "_id" : ObjectId("4ef22e522893ba6797bf8cba"), "first" : "jamie lee", "last" : "curtis", "dob" : "22/11/1958", "gender" : "f", "hair_colour" : "brown", "occupation" : "actor", "nationality" : "american" } |
MapReduce
MapReduce es el big daddy del análisis de datos. En caso de que no hayas escuchado de él, MapReduce es un proceso donde la agregación de datos puede ser dividida y cultivada hacia fuera a través de un conjunto de ordenadores para reducir el tiempo que se tarda en determinar el resultado de una agregación en un conjunto de datos.
Se compone de dos partes: Map y Reduce. Map crea los trabajos que pueden enviarse fuera a los nodos del trabajador para ejecutar el componente Reduce. Reduce entonces computa la respuesta para ese fragmento de trabajo que fue enviado fuera hacia él y devuelve el resultado que se puede combinar con los otros trozos para formar la respuesta final.
Si desea una descripción más específica, esto es lo que Wikipedia tiene que decir al respecto:
MapReduce es una infraestructura para el procesamiento de problemas altamente distribuibles a través de grandes conjuntos de datos usando un gran número de computadoras (nodos), denominadas colectivamente como un cluster (si todos los nodos utilizan el mismo hardware) o una grilla (si los nodos utilizan diferente hardware). El procesamiento computacional puede ocurrir en los datos almacenados en un sistema de archivos (no estructurado) o en una base de datos (estructurados).
Paso "Map": El nodo maestro toma la entrada, la divide en pequeños subproblemas y los distribuye a los nodos de trabajado. Un nodo trabajador, a su vez puede hacer dicho proceso de nuevo, conduciendo a una estructura de árbol multinivel. El nodo trabajador procesa el problema más pequeño y pasa la respuesta a su nodo principal.
Paso "Reduce": El nodo maestro recopila las respuestas de todos los subproblemas y las combina de alguna manera para formar la salida – la respuesta al problema que originalmente estaba tratando de resolver.
Ejemplo MapReduce
Veamos un ejemplo sencillo. Vamos a analizar nuestro conjunto de datos simple y encontrar la cantidad total de todas las hembras del grupo. Ciertamente, este es un ejemplo muy simplista, pero sentará las bases para la comprensión, prácticamente, de cómo funciona MapReduce.
La Función Map
Aquí, vamos a crear una función map que agrupa la información en nuestro conjunto de datos por sexo de la persona y emite un conteo de 1 para cada uno de ellos.
1 |
|
2 |
var map = function() { |
3 |
emit( { gender: this.gender }, { count: 1 } ); |
4 |
}
|
Esto devolverá una salida similar a la siguiente:
1 |
|
2 |
{ 'f' : 1 } |
La Función Reduce
Nuestra función reduce va a tomar la salida de la función map y la utiliza para mantener una ejecución total de la cuenta para cada género. Eche un vistazo a la función reduce a continuación.
1 |
|
2 |
var reduce = function(key, values) { |
3 |
var result = { count : 0 }; |
4 |
|
5 |
values.forEach(function(value){ |
6 |
result.count += value.count; |
7 |
})
|
8 |
|
9 |
return result; |
10 |
}
|
Ejecutando el MapReduce
Ahora, ponemos todo junto llamando la función mapReduce en nuestra base de datos actual. Pasamos a el map y al reduce las variables que hemos creado previamente mediante una llamada a nuestras funciones map
y reduce
y especificar el nombre de la colección en el que el resultado se almacenará; en este caso 'gender'. Para reiterar, el resultado de llamar a la función mapReduce es una colección en nuestra base de datos actual; que podemos iterar como lo haría cualquier otra colección. Echa un vistazo al siguiente código:
1 |
|
2 |
var res = db.nettuts.mapReduce( map, reduce, { out : 'gender' } ); |
Mostrando la salida
Cuando se completa el Map-Reduce, podemos acceder a ella como una colección normal, ejecutando la función find
en ella, como haremos a continuación.
1 |
|
2 |
db.gender.find(); |
3 |
{ "_id" : { "gender" : "f" }, "value" : { "count" : 2 } } |
4 |
{ "_id" : { "gender" : "m" }, "value" : { "count" : 5 } } |
Aquí, ahora tenemos un total por género; 2 hembras y 5 hombres - que se relacionan con nuestro conjunto de datos. Pero qué pasa si queremos filtrar las mujeres del grupo. Bueno, no mucho. Sólo tenemos que hacer uso de la cláusula de consulta que nos permite hacerlo. Por suerte para nosotros, parecerá familiar. Echar un vistazo a la siguiente consulta.
1 |
|
2 |
var res = db.nettuts.mapReduce( map, reduce, { out : 'gender', query : { "gender" : "f" } } ); |
Ahora, cuando mostramos la salida, se verá como a continuación:
1 |
|
2 |
db.gender.find(); |
3 |
{ "_id" : { "gender" : "f" }, "value" : { "count" : 2 } } |
Hay un número de otros parámetros que se pueden pasar a la función de mapReduce para personalizar aún más la salida.
- sort - ordenar la salida retornada
- limit - limitar el número de resultados devueltos
- out - el nombre de la colección para almacenar los resultados
- finalise - especificar una función para ejecutar una vez finalizado el proceso de reducción
- scope - especificar variables que pueden ser utilizadas en el map, reduce y finalise de la función
- jsMode - evita el paso intermedio (entre map y reduce) de convertir a formato JSON
- verbose - seguimiento de las estadísticas sobre el proceso de ejecución
Culminando
Esta ha sido una cobertura de algunos de los temas más complejos de mongoDB. Pero espero que le haya dado más de una muestra de lo que es posible hacer mediante el uso de esta herramienta.
Hemos mirado los operadores condicionales: $lt, $gt, $lte, $gte, $in, $nin, $not
y recorrido a través de una introducción a MapReduce. Espero que hayas conseguido mucho de esto y aprendido más sobre la gran herramienta que es mongoDB.