Un documento completo puede tener el siguiente formato:
{
"People":
[
{
"_id": ObjectId("51c420ba77edcdc3ec709218"),
"nombre": "Manuel",
"apellidos": "Pérez",
"fecha_nacimiento": "1982-03-03",
"altura": 1.80,
"activo": true,
"intereses":["fútbol","tenis"],
"tarjeta_credito": null,
"dni":
{
"numero":"465464646J",
"caducidad":"2013-10-21"
}
},
{
"_id": ObjectId("51c420ba77ed1dc3ec705289"),
"nombre": "Sara",
"apellidos": "Ruano",
"fecha_nacimiento": "1985-12-03",
"altura": 1.65,
"activo": false,
"intereses":["moda","libros","fotografía","política"],
"tarjeta_credito": null
}
]
}
Para realizar consultas a la base de datos, deberemos usar el comando db.nombre_de_coleccion.find(). Este comando puede recibir dos parámetros: una consulta y una proyección. Ambos comandos son opcionales por lo que si ejecutamos el comando:
db.people.find()
Obtendremos una larga lista con los primeros 20 elementos de la colección. Digo primeros, porque MongoDBno muestra todos los elementos. Para la consulta MongoDBcrea un cursor. Es algo que explicaremos en próximas entradas. Si queréis mostrar más documentos deberéis escribir it.
De todas formas al ejecutar el comando podemos ver que el resultado no está demasiado formateado, por lo que es muy difícil leerlo. Para solucionar este problema podemos usar el modificador prettyque nos devolverá un resultado mucho más legible.
db.people.find().pretty()
Ahora vamos a añadir la consulta al comando find, para que filtre los elementos según nuestras necesidades. Para ello especificaremos un objeto JSONcomo primer parámetro del comando, con los campo por los que queremos filtrar:
db.people.find(
{age:34}
).pretty()
Con ese comando obtendremos las personas cuya edad es de 34 años. Podemos añadir tantos filtros como queramos.
db.people.find(
{age:34,isActive:true}
).pretty()
En este caso filtramos por agey por isActive.
Como veis los resultados nos muestran todos los campos de cada elemento. Es como si hubiésemos utilizado el asterisco en una consulta SELECT. Si queremos seleccionar solo algunos de los campos, deberemos utilizar el segundo parámetro de la consulta find para definir una proyección.
db.people.find(
{age:34,isActive:true},
{name:1,age:1,isActive:1}
).pretty()
Que nos devuelve solo los campos que queremos … además de el _id. El _idpor defecto se muestra siempre, así que si queremos ocultarlo hay que especificarlo en la proyección.
db.people.find(
{age:34,isActive:true},
{name:1,age:1,isActive:1,_id:0}
).pretty()
Si queremos mostrar todos los campos, pero quitando solo algunos, lo que haremos será desactivar los no deseados en la proyección:
db.people.find(
{age:34,isActive:true},
{name:0,age:0,isActive:0,_id:0}
).pretty()
Comando findOne
El comando findOnetiene el mismo funcionamiento que el comando find, con la diferencia de que si el comando encuentra más de un resultado que cumpla las condiciones de la consulta, tan solo nos devolverá el primero.
db.people.findOne(
{age:34,isActive:true},
{name:0,age:0,isActive:0,_id:0}
)
Además findOne no acepta pretty, pero ya devuelve el resultado formateado.
Aclaración acerca de las comillas dobles en los identificadores.
Si habéis realizado alguna consulta de prueba siguiendo las indicaciones de este capítulo, probablemente os habréis percatado de que MongoDBguarda los elementos con comillas dobles en el identificador. Es decir que MongoDB guarda las duplas “name”:“Helen” o “age”:34. En las consultas, en cambio, no he especificado dichas comillas. Esto es porque el motor JavaScript de MongoDBse encarga de añadirlas. Esto nos facilita la escritura de consultas, ya que no son obligatorias. De hecho, la siguiente consulta, funcionará perfectamente:
db.people.findOne(
{“age”:34,”isActive”:true},
{“name”:0,”age”:0,”isActive”:0,”_id”:0}
Conclusión
Realizar consultas sencillas con la shell o consola de MongoDBes bastante simple y no tiene demasiadas complicaciones. Pero nos hemos dejado varias cosas en el tintero. Por ejemplo ¿cómo selecciono los documentos cuyo campo age sea menor o igual a 34? ¿Cómo busco dentro de arrays de elementos?
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.
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 AVANZADAS
Consultar arrays
Como ya hemos visto en anteriores entregas de este tutorial, **MongoDB **puede guardar array de elementos. Los elementos que guarda un array pueden ser de cualquier tipo, es decir que pueden ser strings, números, otros arrays o incluso subdocumentos.
En nuestros datos de prueba (podéis ver la primera entrega para saber como importarlos), cada persona de la colección people tiene asociado un campo _ tags_, que es un array de strings. Si queremos buscar un solo elemento dentro de ese array bastará con hacer una consulta similar a la siguiente:
db.people.find({tags:"laborum"},{name:1,tags:1})
En este caso **MongoDB **buscará el elemento "laborum" dentro de el array de _ tags_, devolviendo las personas en cuyo array existe dicho elemento. En este caso la consulta no ha sido diferente de las que hemos hecho anteriormente ya que solo estamos buscando un solo elemento. En cambio si queremos encontrar todos las personas que contengan en _tags _varios valores la consulta sería algo similar a:
db.people.find({tags:{$all:["laborum","sunt"]}},{name:1,tags:1})
Usando el operador $all buscamos varios elementos dentro de un array, especificando como entrada un array de elementos a buscar. En el ejemplo estamos buscando todos las personas que contengan "laborum" y "sunt" en el campo tags. Sólo se devolverán los documentos que contengan ambos valores. En este caso he especificado dos valores, pero podéis añadir todos los que necesitéis y solo se devolverán los documentos que los incluyan.
De manera similar podemos hacer una búsqueda en un array para devolver los documentos que contengan al menos uno de los elementos a buscar
db.people.find({tags:{$in:["laborum","sunt","nisi"]}},{name:1,tags:1})
En este caso he utilizado tres valores y el operador $in, que busca todos los documentos que tengan en el campo _tags _uno de los tres element. En cuanto se detecte que el documento tiene uno de los valores se devuelve como resultado.
Si quisiéramos hacer lo mismo, pero buscando los documentos que **NO **contengan los elementos especificados en el array de entrada, utilizaríamos una consulta con el operador $nin:
db.people.find({tags:{$nin:["laborum","sunt","nisi"]}},{name:1,tags:1})
Otro operador que nos puede ser muy útil es $size, que se utiliza para buscar los documentos que tienen un campo array de un tamaño predeterminado. Es muy sencillo de utilizar. Por ejemplo para devolver todos los documentos cuyo array de tags tiene un tamaño 3 usaríamos la consulta:
db.people.find({tags:{$size:3}})
En cuanto a las proyecciones con arrays, tenemos también operadores muy útiles. Si queremos mostrar solo el primer elemento de un array utilizaremos la siguiente consulta:
db.people.find({tags:{$in:["laborum","sunt","nisi"]}},{"tags.$":1,name:1})
La parte que filtra los datos es la que hemos utilizado en un ejemplo anterior, pero hemos modificado un poco la proyección. Recordad que una proyección se utiliza para mostrar los campos concretos que queremos devolver (como los campos de una sentencia SELECT de SQL). En este caso utilizamos entre comillas el operador $. En este caso lo que hacemos con el "tags.$":1 es devolver el primer elemento del array de tags.
Otro operador interesante para proyecciones es $slice. Con este operador lo que hacemos es devolver un número determinado de elementos de un array.
db.people.find({tags:{$in:["laborum","sunt","nisi"]}}, {tags:{$slice:3},name:1})
Con la consulta anterior devolveremos los documentos filtrados, pero solo devolveremos dos campos: los tres primeros elementos del array de _tags _y el nombre de la persona. Si quisiéramos devolver los tres últimos bastaría con usar un número negativo.
db.people.find({tags:{$in:["laborum","sunt","nisi"]}}, {tags:{$slice:-3},name:1})
Y ya para nota tenemos la opción de usar como parámetro de $slice un array del tipo [skip,limit]
db.people.find({tags:{$in:["laborum","sunt","nisi"]}}, {tags:{$slice:[2,3]},name:1})
O lo que es lo mismo, se ignoran los dos primeros elementos del array (skip) y se cogen sólo los 3 siguientes (limit). En el caso de querer empezar a buscar por el final del array _skip _tiene que ser un número negativo.
db.people.find({tags:{$in:["laborum","sunt","nisi"]}}, {tags:{$slice:[-2,3]},name:1})
Dot Notation
Dot Notation (algo así como notación punto) se utiliza en **MongoDB **para realizar consultas en arrays y en subdocumentos. Se basa en añadir un punto después del identificador del array o subdocumento para realizar consultas sobre un índice en concreto del array o sobre un campo concreto del subdocumento. Un ejemplo con arrays:
db.people.find({"tags.1":"enim"})
En el ejemplo buscamos todos los documentos que cumplan la condición de que el valor 1 del array sea "enim". Dos cosas importanes, los arrays empiezan con el índice 0 y es necesario que “tags.1” vaya entre comillas para no recibir un error en la Shell.
Consultas en subdocumentos
En nuestros datos de ejemplo existe un campo llamado friends, que contiene un array de subdocumentos. Si en dicho campo quisiéramos buscar los elementos que contienen el subdocumento compuesto por el id 1 y el nombre "Trinity Ford" utilizaríamos una consulta como esta:
db.people.find({ friends: { id:1, name:"Trinity Ford" } })
En este caso solo se devuelve los documentos que en el array del campo friends tienen el subdocumento_ {id:1,name:”Trinity Ford”}_. Para buscar por un campo del subdocumento en concreto deberemos usar Dot Notation.
`
db.people.find({"friends.name":"Trinity Ford"}) `
Se puede ver que entre comillas hemos especificado el campo "friends.name", lo que quiere decir que tenemos que buscar en el subdocumento friends, por el campo name. En este caso se devuelven todos los documentos que cumplen el _ “friends.name”:”Trinity Ford”_ independientemente del _id _que tengan.
Usando Dot Notation, podemos hacer consultas más precisas y complejas:
db.people.find({"friends.2.name":{$gte:"T"}}, {friends:{$slice:-1},name:1})
Buscamos en el array _friends _los elementos que estén en la posición 2 y cuyo nombre sea mayor o igual que T. Además en la proyección mostramos el último elemento, que es por el que estamos filtrando.
Búsquedas en campos de texto con expresiones regulares
Hemos visto que los operadores $gt, $gte, $lt etc. se pueden utilizar con _ strings_. Pero ¿cómo podemos buscar patrones en el texto de los campos? Para eso utilizaremos el operador $regex, que utilizando expresiones regulares, nos permite hacer búsquedas más complejas en los campos de tipo texto.
Como cada lenguaje de programación utiliza las expresiones regulares de manera diferente, debemos especificar que **MongoDB **utiliza Perl Compatible Regular Expressions. Este tutorial no pretende profundizar en las expresiones regulares así que si queréis más información podéis ver este enlace con información sobre las expresiones regulares en Perl. Aquí tenemos un ejemplo de consulta con expresión regular:
db.people.find({"name": {$regex:".*r$"}},{name:1})
En este caso buscamos todos los documentos cuyo nombre termine con la letra_ r_ mínuscula.
db.people.find({"name": {$regex:".*fis"}},{name:1})
db.people.find({"name": {$regex:".*Fis"}},{name:1})
En las dos consultas anterories estamos buscando elementos que contengan _ “fis”_ o "Fis". Por defecto las expresiones regulares son sensibles a mayúsculas por lo que la primera consulta no devuelve resultados. Si queremos ignorar las mayúsculas deberemos utilizar el opeardor $options.
db.people.find({"name": {$regex:".*fis", $options:"i"}},{name:1})
Con la opción i, le decimos a **MongoDB **que las comparaciones no serán sensibles a mayúsculas. Además de la opción i hay varias opciones que podéis consultar en la ayuda de MongoDB.
Aunque las expresiones regulares pueden ser útiles, no conviene abusar de ellas. No todas pueden hacer uso de los índices y pueden hacer que nuestras consultas sean muy lentas. Así que hay que usarlas con cuidado.
Cursores
Cuando hacemos una consulta en la Shell de MongoDB, el servidor nos devuelve un objeto cursor. Un cursor no es más que un iterador sobre los resultados de una consulta. El cursor está en el servidor, mientras que en el cliente solo tenemos el identificador del mismo. Con este sistema se evitan mover datos innecesarios del servidor al cliente, ya que el cursor por defecto solo devuelve 20 resultados. Si queremos mostrar más, debemos escribir “it” en la consola, lo qué nos devolverá los siguientes 20 resultados.
Lo bueno de los cursores es que tienen una serie de opciones interesantes que podemos utilizar para contar el número de resultados u ordenarlos.
Count
Devuelve el número de documentos devueltos por la consulta.
db.people.find({"friends.2.name":{$gte:"T"}}).count()
Sort
Ordena los resultados por el campo especificado. La siguiente consulta ordena de forma ascendente
db.people.find({"friends.2.name":{$gte:"T"}},{_id:0,name:1}).sort({name:1})
Y la siguiente consulta ordena de forma descendente.
db.people.find({"friends.2.name":{$gte:"T"}},{_id:0,name:1}).sort({name: -1})
Podemos especificar más de un campo separándolo por comas.
db.people.find({"friends.2.name":{$gte:"T"}},{_id:0,name:1,email:1}).sort({name:1,email:1})
Limit
Limita el número de resultados devuelto. El siguiente ejemplo devuelve los 5 primeros documentos.
db.people.find({"friends.2.name":{$gte:"T"}},{name:1}).limit(5)
Skip
Ignora los N primeros documentos especificados. El siguiente ejemplo salta los 5 primeros documentos y devuelve los siguientes
db.people.find({"friends.2.name":{$gte:"T"}},{name:1}).skip(5)
toArray
Guarda los resultados en un array que podemos asignar a una variable
var myArray = db.people.find({"friends.2.name":{$gte:"T"}},{name:1}).toArray()
Lo bueno de todos estos comandos, es que se pueden concatenar. Por ejemplo para saltar los 10 primeros documentos devueltos y coger los 5 siguientes podemos usar la siguiente consulta
db.people.find({"friends.2.name":{$gte:"T"}},{name:1}).skip(10).limit(5)
También podemos ordenar los resultados por orden ascendente y coger solo el primero, devolviendo el valor más bajo.
db.people.find({"friends.2.name":{$gte:"T"}}, {name:1}).sort({name:1}).limit(1)
Conclusiones
En esta entrada hemos profundizado en las consultas de MongoDB. Hemos aprendido a consultar sobre arrays, a usar Dot Notation, expresiones regulares y echado un vistazo a alguna de las operaciones que podemos hacer con los cursores.
Ahora que sabemos realizar consultas, nos toca aprender a realizar modificaciones. Pero eso será en futuras entradas.