Tutorial MongoDB. Operaciones de consulta avanzadas II

Nota: Esta serie de artículos pertenece al tutorial que escribí hace unos años sobre MongoDB. La versión de MongoDB ha sido actualizada varias veces, pero podéis ver el resto de los artículos de la serie en este enlace

Volvemos a la carga con nuestro tutorial de MongoDB, continuando con las operaciones de consulta avanzadas que ya empezamos la semana pasada.

En la anterior entrega vimos como realizar consultas complejas con find o findOne, incluyendo en ellas operadores lógicos como $gt, $lt, $exists, $and y algunos más. En esta entrada nos vamos a centrar en las consultas sobre elementos complejos, como  los arrays y los subdocumentos. Así que vamos a ello.

Recuerda: que para los ejemplos estamos utilizando el conjunto de datos que puedes descargar de aquí. Si todavía no sabes como importar estos datos a tu base de datos **MongoDB **tienes una pequeña guía de como hacerlo aquí. Si aún no tienes instalado **MongoDB **aquí explico como puedes hacerlo.

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.

En cuanto a los subdocumentos, lo vemos en el siguiente apartado.

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.



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