Tutorial MongoDB. Operaciones de consulta avanzadas I

Nota: Recuerda que este artículo foma parte del tutorial de MongoDB, al que puedes acceder desde este enlace. En el tutorial explico como instalar MongoDB, como conectar a la base de datos o cosas más avanzadas com Aggregation Framework y conjuntos de réplicas.

En el pasado artículo, hicimos una aproximación a los comandos find y findOne, para realizar consultas sencillas sobre MongoDB. En esta entrada profundizaremos un poco más y explicaremos consultas más complicadas. Inicialmente había pensado explicar todo en un solo artículo, pero creo que este hubiera sido demasiado largo, así que vamos a dividirlo en dos partes. 

 
Tanto en esta parte, como en la siguiente, utilizaremos muchos de los operadores existentes en MongoDB. Podéis encontrar una lista detallada aquí. Como para realizar consultas necesitamos datos, vamos a utilizar el mismo archivo JSON que en el anterior artículo. Podéis descargarlo de mi SkyDrive. Si no sabéis como importarlo, recordad que está explicado en el anterior post.
 
Una vez hecha la introducción aquí va la primera entrega de consultas avanzadas en la Shell de MongoDB.

Operaciones de comparación

En las bases de datos relacionales, es muy típico filtrar los resultados según el valor de un determinado campo. Por ejemplo si X > 0, Y. Pero ¿cómo realizamos esto en MongoDB?
 
Imaginemos que queremos mostrar las personas de la colección people, que tienen más de 30 años. Para ello utilizaremos el operador $gt (abreviatura de “greater than” en inglés).
> db.people.find({age:{$gt:30}},{name:1,age:1})
Como veis la consulta es bastante sencilla. Como primer parámetro del comando find añadimos la consulta y como segunda parte una proyección con los datos que queremos que nos devuelva dicha consulta (en este caso name y age). Todo en MongoDB se hace con JSON así que para buscar los mayores de 30 años añadimos otro documento JSON con el operador utilizado y el valor por el que debe filtrar.
 
Si os fijáis en los resultados, no se devuelve ninguna persona con edad igual a 30. Esto es porque hemos usado $gt y no $gte (“greater than equals” en inglés). Así que si ejecutamos la consulta siguiente, obtendremos todos elementos de la colección people con edad mayor o igual a 30 años.

> db.people.find({age:{$gte:30}},{name:1,age:1})
Lo mismo haremos si queremos obtener las personas menores de 30 años utilizando los comandos $lt (“lower than”) o $lte (“lower than equals”).
> db.people.find({age:{$lt:30}},{name:1,age:1})
Y si quisieramos extraer todas las personas cuya edad NO es 30 utilzaríamos el operador $ne.
> db.people.find({age:{$ne:30}},{name:1,age:1})
Imaginemos ahora que queremos extraer las personas con una edad igual a 25, 30 o 35 años. En SQL podríamos utilizar un WHERE age IN (25,30,35). En MongoDB utilizaríamos el operador $in y un array con los datos.
> db.people.find({age:{$in:[25,30,35]}},{name:1,age:1})

Esta consulta nos devuelve todos las presonas cuyas edades son igual a 25, 30 o 35.

Comparaciones con strings

Los operadores descritos anteriormente son aplicables a los strings. Pero hay que tener en cuenta que MongoDB distingue entre mayúsculas y minúsculas y que utiliza el orden lexicográfico. Esto quiere decir que MongoDB ordena los strings de la misma manera que un diccionario, aunque diferenciando mayúsculas y minúsculas.
 
Este es un ejemplo de orden de strings que hace MongoDB.
{ "_id" : "AAab" }
{ "_id" : "Abb" }
{ "_id" : "Abc" }
{ "_id" : "BCb" }
{ "_id" : "Bbaab" }
{ "_id" : "abb" }
{ "_id" : "abc" }
{ "_id" : "bcb" }
En orden ascendente las mayúsculas van primero y luego se tiene en cuenta el orden lexicográfico de cada letra. Como podéis ver el número de caracteres no se tiene en cuenta.

Operaciones según la existencia o el tipo de los elementos

Como ya sabéis MongoDB es una base de datos sin esquema, lo que quiere decir que los documentos, aun siendo de la misma colección, pueden tener distintos campos. Incluso estos campos pueden ser de distintos tipos en cada documento.
 
Así que en ocasiones puede ser útil realizar una consulta que nos devuelva los documentos en los que exista un determinado campo. En la siguiente consulta comprobamos buscamos los documentos en los que existe el campo company.
> db.people.find({company:{$exists:true}},{name:1,age:1,company:1})
Si quisieramos buscar los documentos que no tienen el campo company, bastará con cambiar el true por un false en el $exists.
 
MongoDB puede guardar documentos con distintos tipos en el mismo campo. Por ejemplo aunque age es un número para todos los documentos, podríamos insertar un documento nuevo con un string en ese campo. Por tanto, también podríamos necesitar filtrar por documentos en los cuales un campo será de un determinado tipo. Esto se hace con el operador $type.
> db.people.find({company:{$type:2}},{name:1,age:1,company:1})
En este caso estamos buscando los documentos cuyo campo company sea de tipo 2, que es el tipo string. Podéis encontrar número asignado a cada tipo en la ayuda del operador $type en la página de MongoDB.

Operaciones lógicas

En bases de datos relacionales es muy típico añadir operadores OR a la clausula WHERE. Por ejemplo WHERE gender = “female” OR age > 20. Hasta ahora las consultas que hemos visto buscaban por uno o más campos, pero lo hacían  con la lógica de un AND, es decir, que todas las condiciones debían cumplirse. Si queremos añadir cláusulas OR usaremos $or.
> db.people.find( 
{ $or:
[
{gender:"female"},
{age:{$gt:20}}
]
} ,
{name:1,gender:1,age:1} )
En este caso buscamos los documentos cuyo campo gender sea “female“ o cuyo campo age sea mayor que 20. Como vemos basta con especificar un array de condiciones para que el operador $or realice la consulta.
 
Lo curioso es que también existe un operador $and. ¿Por qué es curioso? Pensemos en las siguientes consultas
> db.people.find( {gender:"female", age:{$gt:20}} , {name:1,gender:1,age:1} )
> db.people.find(
{
$and:
[
{gender:"female"},
{age:{$gt:20}}
]
} ,
{name:1,gender:1,age:1} )
Pues es curioso, porque en realidad, la consulta es la misma. En la primera, MongoDB hace un and implícito de los parámetros de la consulta, mientras que en la segunda hemos incluido el and explícitamente
 
¿Entonces por qué usar este operador? Pues puede utilizarse si tenemos que hacer una consulta que incluya dos veces el mismo campo. Si no utilizamos el and y lo hacemos de de esta manera, podemos obtener resultados erróneos. Por ejemplo las siguientes consultas son iguales, aunque invirtiendo el orden de las condiciones. 
> db.people.find( {age:{$gt:30},age:{$lt:40}} , {name:1,gender:1,age:1} )
> db.people.find( {age:{$lt:40},age:{$gt:30}} , {name:1,gender:1,age:1} )
Si las ejecutáis veréis que devuelven resultados distintos. Esto es porque MongoDB coge el último valor para realizar la consulta. La consulta correcta sería:
> db.people.find( 
{
$and:
[
{age:{$gt:30}},
{age:{$lt:40}}
]} ,
{name:1,gender:1,age:1} )
$and también puede ser útil para filtrar conjuntamente con $or
db.people.find( 
{$or:
[ {age:{$gt:30}},
{$and:[
{age:{$gt:50}},
{gender:"female"}
]
}
]
})
Este comando nos buscará las personas en people cuya edad es mayor que 30, o cuyo género es “female” y su edad mayor que 50.
Además de $and y $or, tenemos otros dos operadores lógicos que son $not y $nor.
$not es bastante sencillo de entender ya que lo que hace es buscar los documentos que no cumplan una determinada condición. 
> db.people.find( {age:{$not:{$gt:30}}})
Lo importante en este caso, es saber que $not solo puede usarse con otros operadores como $gt o $lt. No puede usarse con valores directos o documentos. Para eso ya existe el operador $ne que hemos explicado antes. También hay que tener en cuenta que estamos buscando edades que no sean mayores que 30. Esto incluye el 30, ya que está fuera del conjunto de números mayores que 30 , y los documentos que no tengan campo age.
También podemos utilizar el operador $nor que acepta dos o más valores. Por ejemplo en la siguiente consulta buscamos las personas cuya edad NO sea mayor que 30 y cuyo campo isActive NO sea true. 
> db.people.find({$nor:[{age:{$gt:30}},{isActive:true}]},{age:1,isActive:1})
Destacar que al igual que $not, $nor devuelve también los documentos si los campos no existen. Para evitar esto, si es algo que no deseamos, podemos añadir el operador $exists.
db.people.find(
{$nor:
[ {age:{$gt:30}},{age:{$exists:false}},
{isActive:true},{isActive:{$exists:false}}
]
})
En este caso buscamos los documentos cuya edad NO sea mayor que 30, cuyo campo isActive NO sea true y que ambos campos existan.

Conclusión

MongoDB tiene un potente motor de consultas que nos permite realizar prácticamente cualquier operación. No obstante en este artículo hemos dejado fuera partes importantes, como las consultas sobre campos que contienen un array o un subdocumento, consultar texto como si usáramos el LIKE de las bases de datos relacionales o como realizar ordenaciones.
 
Todo eso para la siguiente entrada. Allí nos vemos.




¿Te ha gustado el artículo? No te olvides de hacer +1 en Google+, Me gusta en Facebook o de publicarlo en Twitter. ¡ Gracias !



Recuerda que puedes ver el índice del tutorial y acceder a todos los artículos de la serie desde aquí.



¿Quiéres que te avisemos cuando se publiquen nuevas entradas en el blog?

Suscríbete por correo electrónico o por RSS