Noob, te presento a Mongo

“The database market is in need of a big change. The technology that people typically use, the database layer, was designed in 1970 with a very different set of requirements in mind […] It’s common to have applications for all your customers, your partners, your prospects, all interacting over the internet. It just wasn’t in mind when the relational database was built.” Max Schireson

Cuando uno se inicia en una base de datos no relacional como MonboDB se siente realmente perdido. No hay tablas, no hay registros, no hay SQL y NO HAY RELACIONES. Entonces… ¿Por dónde empiezo?

A lo largo de este artículo vamos a pasar a través de conceptos teóricos hasta llegar a un uso práctico:

  • ¿Qué es MongoDB y Cómo funciona?
  • ¿Cuándo usarlo y cuándo no?
  • ¿Cómo lo instalo?
  • Terminología, y formato de los documentos e ideas para la organización de datos
  • Comandos útiles en la consola de MongoDB
  • ¿Cómo consulto los datos?
  • ¿Cómo inserto los datos?
  • ¿Cómo hago un update? Parámetros especiales de update
  • ¿Cómo hago un delete?
  • Operaciones avanzadas de consulta

¿Qué es MongoDB y Cómo funciona?

Existen varios tipos de BD no SQL, por ejemplo, de clave-valor, documentos, orientadas a grados, etcétera. MongoDB es una base de datos orientada a documentos. Esto quiere decir que los datos ya no se guardan en registros, sino que se guardan en documentos. Estos documentos se almacenan en JSON, bueno, para ser exactos son almacenados en BSON (representación binaria de JSON). En MongoDB no existen tablas, existen colecciones, y una de las características más importantes es que no es necesario seguir un esquema, es decir, documentos de una misma colección pueden tener esquemas diferentes.

Imaginemos que tenemos una colección a la que llamamos Camisetas. Un documento podría almacenarse de la siguiente manera:

En la misma colección podríamos guardar un documento como este:

Esto es algo totalmente válido en MongoDBEste nuevo documento no sigue el mismo esquema que el primero. Tiene menos campos, algún campo nuevo que no existe en el documento anterior e incluso un campo de distinto tipo.

Las características que más destacaría de MongoDB son su velocidad y su rico pero sencillo sistema de consulta de los contenidos de la base de datos. Se podría decir que alcanza un balance perfecto entre rendimiento y funcionalidad, incorporando muchos de los tipos de consulta que utilizaríamos en nuestro sistema relacional preferido, pero sin sacrificar en rendimiento.

Hasta aquí tenemos claro que MongoDB es una base de datos no relacional que utiliza colecciones y que guarda los datos en documentos. Vamos a ver cómo funciona. MongoDB está escrito en C++, aunque las consultas se hacen pasando objetos JSON como parámetro. Es algo bastante lógico, dado que los propios documentos se almacenan en BSON. Por ejemplo:

La consulta anterior buscará todas las camisetas cuyo estilo sea “Casual”.

MongoDB viene de serie con una consola desde la que podemos ejecutar los distintos comandos. Esta consola está construida sobre JavaScript, por lo que las consultas se realizan utilizando ese lenguaje. Además de las funciones de MongoDB, podemos utilizar muchas de las funciones propias de JavaSciprt. En la consola también podemos definir variables, funciones o utilizar bucles.

¿Cuándo usarlo y cuándo no?

Hay muchísimos factores que pueden aparecer para responder a esta pregunta. Yo voy a explicarlo de una manera conceptual pero clara, con algún ejemplo real.

SI

MongoDB se puede usar en las típicas aplicaciones CRUD o de muchos de los desarrollos web y móvil actuales.

MongoDB es especialmente útil en entornos que requieran escalabilidad. Con sus opciones de replicación y sharding, que son muy sencillas de configurar, podemos conseguir un sistema que escale horizontalmente sin demasiados problemas.

Lo dije al principio, pero me gustaría repetirlo. Las características que más destacaría de MongoDB son su velocidad y su rico pero sencillo sistema de consulta de los contenidos de la base de datos. Se podría decir que alcanza un balance perfecto entre rendimiento y funcionalidad, incorporando muchos de los tipos de consulta que utilizaríamos en nuestro sistema relacional preferido, pero sin sacrificar en rendimiento.

NO

Aquí debemos partir de las desventajas que presenta noSQL frente a SQL, como pueden ser:

  • Puede que SQL tenga mayor consistencia.
  • No tienen estados transnacionales. Aunque nuestra aplicación puede utilizar alguna técnica para simular las transacciones, MongoDB no tiene esta capacidad. Solo garantiza operaciones atómicas a nivel de documento. Si las transacciones son algo indispensable en nuestro desarrollo, deberemos pensar en otro sistema.
  • En Mongo no hay Join (puesto q no existen relaciones). Para consultar datos relacionados en dos o más colecciones, tenemos que hacer más de una consulta. En general, si nuestros datos pueden ser estructurados en tablas, y necesitamos las relaciones, es mejor que optemos por un sistema clásico relacional.

¿Cómo lo instalo?

No hay nada más sencillo. Descargamos desde la web de MongoDB.org  los binarios para nuestro sistema operativo y ejecutamos. Aún así, si tenemos cualquier duda, podemos consultar las guías de instalación.

Una vez bajados podremos arrancar el servicio de MongoDB con un solo comando.

Con este comando arrancamos el servicio mongod, que empezará a escuchar peticiones por el puerto 27017. Es importante indicar el parámetro —dbpath, con la ruta dónde se almacenarán los ficheros de nuestra base de datos. En mi caso (trabajo en MAC OS X), tengo la carpeta mongdb situada en la ruta /Users/pb/Desarrollo/mongodb/data/db, por tanto, para lanzar mongo lo que hago es:

Si ya tenemos el servidor lanzado en nuestra máquina, bastará con lanzar desde la consola el siguiente comando:

Yo lo que hago es abrir una nueva ventana en el terminal y ejecutar dicho comando. Desde ese momento entraremos en la consola y podremos realizar consultas. Y como siempre, si escribimos help veremos un listado con los comandos más comunes y su descripción.

Terminología, formato de los documentos e ideas para organizar los datos

Ya hemos ido comentando cositas, pero considero importante aclarar algunos términos una vez más. En MongoDB, cada registro o conjunto de datos se denomina documento. Los documentos se pueden agrupar en colecciones, las cuales se podría decir que son el equivalente a las tablas en una base de datos relacional (sólo que las colecciones pueden almacenar documentos con muy diferentes formatos, en lugar de estar sometidos a un esquema fijo). Se pueden crear índices para algunos atributos de los documentos, de modo que MongoDB mantendrá una estructura interna eficiente para el acceso a la información por los contenidos de estos atributos. Los documentos se almacenan en formato BSON.

Respecto al formato, en la práctica, nunca veremos el formato en que verdaderamente se almacenan los datos (BSON), y trabajaremos siempre sobre un documento en JSON tanto al almacenar como al consultar información.

El atributo “_id” (o clave principal) lo genera MongoDB automáticamente, siempre que no lo especifiquemos en la inserción. Digamos que es como la clave principal que todo documento debe tener. Todos los documentos tienen este campo y tiene que ser único. El campo _id, como cualquier campo de MongoDB, puede ser de cualquier tipo. Podemos insertar números, texto o como hace MongoDB un ObjectId. Una vez insertado, el campo _id no se puede modificar. Si queremos hacer algo parecido, tenemos que borrar el documento, e insertarlo otra vez, pero con un _id distinto. Os pongo primer un ejemplo y luego os doy la explicación:

A pesar de parecer un valor completamente aleatorio, utilizan como base una semilla basada en la MAC de la interfaz de red de la máquina (y otros detalles de la misma) para evitar que dos máquinas diferentes puedan generar el mismo valor para la clave de un documento. Y los primeros bytes corresponden a una marca de tiempo, de modo que las claves se ordenan de forma natural por orden de creación (o casi, pues está claro que las distintas máquinas corriendo MongoDB deben tener la fecha y hora sincronizadas) sin tener que mirar cuál fue el último valor usado. Este atributo “_id” es el único obligatorio para un documento.

Otra cosa, cuando busquemos en un documento, no es necesario pedir a MongoDB que nos devuelva todo el documento cada vez que lo consultamos, sino que podemos utilizar filtros. Así, ahorramos ancho de banda entre el motor de base de datos y la aplicación, así como memoria.

El tamaño de un documento de MongoDB puede llegar hasta los 16 Megabytes, con lo que podemos almacenar bastante información dentro de un único documento sin necesidad de utilizar referencias, si así lo necesitamos. En caso de que tuviéramos que almacenar mucho más, habría que optar por utilizar otro esquema.

A veces, toca desnormalizar.

Es por eso que en el atributo “author_info” he utilizado una versión intermedia: Si bien tenemos la clave principal del usuario, como es habitual que necesitemos el nombre del autor, he almacenado también dicho nombre en el documento, para que no sea necesario realizar una segunda consulta a la colección “X”. Estas desnormalizaciones dependen nuevamente del uso que se den a los datos. En este caso, tengo claro que el nombre de un usuario no va a cambiar demasiado.

El modelado del esquema de datos con MongoDB depende más de la forma en que consultaremos o actualizaremos los datos que de las limitaciones del propio sistema.

Comandos útiles en la consola de MongoDB

Nada más conectarnos a la consola o shell de MongoDB, con el comando mongo, tenemos disponibles muchas funciones que nos pueden ayudar a recordar comandos o los parámetros que estos utilizan.

  • show dbs, la consola nos mostrará las bases de datos existentes y el espacio que están consumiendo.
  • use nombredb, podremos establecer el contexto para que a partir de entonces todas las consultas realizadas se hagan sobre dicha base de datos.
  • Una vez estemos en dicho contexto podemos escribir el comando show collections, que nos mostrará las colecciones existentes en la base de datos que estamos utilizando.
  • Si escribimos help veremos una lista de comandos generales que podemos utilizar en la consola.
  • Con el comando db.help() veremos la ayuda de los comandos aplicables a la base de datos .
  • Con el comando db.nombrecoleccion.help(), podremos ver los comandos aplicables a una colección en concreto.

¿Como consulto los datos?

Tipos de datos

Ya hemos visto que MongoDB guarda los datos utilizando JSON. En realidad los datos se guardan en BSON (una representación binaria de JSON), pero como nosotros vamos a realizar consultas y manipular los datos con JSON, será en lo que nos centremos.

MongoDB, a través de JSON, puede utilizar los siguientes tipos:

  • String: guardados en UTF-8. Van siempre entre dobles comillas.
  • Number: números. Al guardarse en BSON pueden ser de tipo  byte, int32, in64 o double.
  • Boolean: con valor true o false.
  • Array: van entre corchetes [] y pueden contener de 1 a N elementos, que pueden ser de cualquiera de los otros tipos.
  • Documentos: un documento en formato JSON puede contener otros documentos embebidos que incluyan más documentos o cualquiera de los tipos anteriormente descritos.
  • Null.

Consulta

Ahora que tenemos controlados los tipos de datos que podemos usar con MongoDB, que sabemos los comandos básicos que tiene la consola, y que sabemos manejarnos con la ayuda, vamos a ver como realizar consultas.

MongoDB nos permite utilizar funciones Map y Reduce escritas en Javascript para seleccionar los atributos que nos interesan de los datos, y agregarlos (unificarlos, simplificarlos) en la manera deseada, respectivamente. Esto es algo habitual en muchos sistemas NoSQL, y en algunos casos es incluso la única forma posible de consultar datos. Claro está que muchas veces necesitamos algo bastante más sencillo que ésto.

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:

Obtendremos una larga lista con los primeros 20 elementos de la colección. Digo primeros, porque MongoDB no muestra todos los elementos. Para la consulta MongoDB crea un cursor. Lo veremos más adelante. Si queréis mostrar más documentos deberéis escribir it.

Al ejecutar el comando de consulta .find() 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 pretty que nos devolverá un resultado mucho más legible.

Podemos hacer consultas al valor de un atributo específico. Por ejemplo, buscar una camiseta por el nombre interno (alias) que le ha dado la compañía.

También podemos hacer consultas con varios datos de búsqueda. Por ejemplo, con el siguiente comando obtendremos las personas cuya edad es de 34 años y que estén activos. Es decir, en este caso filtramos por age y por isActive. Podemos añadir tantos filtros como queramos.

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.

Que nos devuelve solo los campos que queremos … además de el _id. El _id por defecto se muestra siempre, así que si queremos ocultarlo hay que especificarlo en la proyección.

Es decir, poniendo un 0 (cero) ocultamos aquellos que no nos interesen. En el ejemplo fue el _id, pero podría haber sido cualquiera.

El comando findOne tiene 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. Este comando no acepta pretty.

El valor a consultar puede estar anidado en un tipo de datos más completo en el atributo del documento (por ejemplo, como valor de un hash asociado al atributo, o como el valor de uno de los ítems de un array). Se utiliza un punto como separador de los nombres de las claves de los diferentes hashes que hay que recorrer hasta llegar al valor deseado. Por ejemplo, relacionado con el código que vimos antes, la siguiente consulta devolvería las camisetas hechas por un diseñador:

Aclaración acerca de las comillas dobles en los identificadores

Si habéis ido experimentando, probablemente os habréis percatado de que MongoDB guarda los elementos con comillas dobles en el identificador. Es decir que MongoDB guarda las duplas “name”:”Pablo Ojeda” o “age”:34. En las consultas, en cambio, no he especificado dichas comillas. Esto es porque el motor JavaScript de MongoDB se encarga de añadirlas. Esto nos facilita la escritura de consultas, ya que no son obligatorias.

¿Cómo inserto los datos?

En primer lugar debemos olvidarnos de SQL. Como ya he dicho varias veces, MongoDB es una base de datos orientada a documentos que se guardan en BSON, que es una forma de representar de forma binaria objetos JSON.

Insertar documentos es muy sencillo. Solo hay que conectarse la consola y escribir el comando db.nombre_coleccion.insert. Como parámetro pasaremos el objeto JSON que queremos insertar. ¿Os estáis preguntando cómo inserto en una colección si todavía no la he creado? Pues fácil y sencillo; MongoDB la crea automáticamente.Por ejemplo, si queremos insertar en la colección speakinbytes:

Como se puede ver, la inserción es muy sencilla. Nos basta con pasar como parámetro el documento JSON que queremos insertar. Si ahora hacemos un find(), veremos que se ha creado la colección y el documento. El comando find(), es algo parecido a un SELECT * FROM speakinbytes.

Nota: Para ver los resultados de manera ordenada y no de forma caótica, utilizar el .pretty()

En el documento que devuelve el comando find(), además de los campos que hemos creado, aparece uno denominado _id. Ya lo explicamos en un apartado anterior, y este campo lo generá MongoDB automáticamente, siempre que no lo especifiquemos en la inserción. Digamos que es como la clave principal que todo documento debe tener. Todos los documentos tienen este campo y tiene que ser único. El campo _id, como cualquier campo de MongoDB, puede ser de cualquier tipo. Podemos insertar números, texto o como hace MongoDB un ObjectId. Una vez insertado, el campo _id no se puede modificar. Si queremos hacer algo parecido, tenemos que borrar el documento, e insertarlo otra vez, pero con un _id distinto.

Insertando de forma masiva

Otra opción interesante del comando insert, es que permite insertar varios documentos JSON en una misma inserción. Si queremos hace múltiples inserciones,  deberemos pasar como parámetro un array de documentos en lugar de un solo documento. De esta manera MongoDB optimizará las inserciones, haciendo que sean mucho más rápidas:

En este caso hemos creado 5 documentos, pero podemos insertar miles de ellos. Tan solo hay que tener en cuenta que MongoDB no acepta comandos de más de 48 MB. Si los insertamos desde la consola, tendremos que separarlos de forma manual para no superar ese espacio. Si lo hacemos con el driver de algún lenguaje de programación, algunos de ellos son capaces de partir los documentos de forma automática.

Save. Para insertar documentos también podemos utilizar el comando save, que se utiliza de la misma manera que insert. No son iguales, ya que con save, si el _id que especificamos ya existe,  el documento será modificado.

Para hacer pruebas con una gran cantidad de datos, ricos y bien estructurados, lo ideal es usar un generador de JSON como JSON Generator.

16 MB de tamaño máximo. Ya hemos visto que los documentos pueden tener cualquier tipo de esquema. Además de los típicos números o cadenas, también pueden contener arrays o subdocumentos. Pero además hay que tener en cuenta cuando hagamos inserciones que los documentos no pueden tener un tamaño superior a 16 MB.

¿Cómo hago un update de un campo de un documento?

Actualizar datos es algo más complejo. Primero porque hay que usar una consulta para filtrar los documentos y otra sentencia para actualizar los campos. Y segundo porque las actualizaciones de datos, pueden implicar que MongoDB tenga que mover documentos. Y eso afecta al rendimiento. Además la consultas pueden ser complicadas si tenemos que actualizar arrays o subdocumentos. Por ello, en este artículo, sólo vamos a tratar la modificación de datos sencillos, que ya es un buen comienzo.

Para actualizar datos sencillos, utilizamos la sentencia update. Esta sentencia funciona de manera similar a como funciona en una base de datos relacional. Veamos un ejemplo. Supongamos que tenemos el documento anterior:

Para actualizar utilizando el update haríamos lo siguiente:

La primera parte de la sentencia, la que contiene el campo _id, es la que utilizamos para filtrar. Es como si en SQL hicieramos un “Where _id = ‘5305eae128222ca13a01b039’“. La segunda parte de la sentencia, expresa el valor que tendrá el documento tras la actualización. Y esto es importante, no le estamos diciendo a MongoDB que modifique solo el campo name. Estamos diciendo que modifique el documento entero. Si después de actualizar, hacemos una búsqueda con un db.speakinbytes.find() el documento tendrá este aspecto:

Tras un update MongoDB ha modificado el documento por completo.

¿Qué podemos hacer para que sólo se modifique un valor del documento y no el documento entero? Utilizar el modificar $set

Volviendo al documento original, vamos a hacer la misma operación que antes, pero en esta ocasión modificando solo el campo username. Para ello utilizamos el modificador $set.

En este caso sólo se modifica el campo afectado, quedando el documento así:

Parámetros especiales de update

Los ejemplos anteriores utilizan el _id del documento para filtrar los documentos que hay que actualizar. Como consecuencia solo se actualiza un documento, ya que este campo siempre es único. Pero aunque nuestro filtro devolviese más de un documento, MongoDB solo actualizaría uno de ellos. Para que MongoDB actualice más de un documento con una sentencia update, se lo tenemos que decir de forma expresa con el parámetro multi, ya que por defecto MongoDB solo actualiza un documento a no ser que le indiquemos lo contrario. Es una manera de evitar errores. Para actualizar todos deberemos usar la opción multi que debe incluirse como tercer parámetro.

Además de la opción multi, tenemos disponible la opción upsert, que lo que hace es insertar el documento si este no existe. Es bastante parecido al comando save que hemos visto en las operaciones de inserción. En este caso, se comprueba toda la consulta en lugar de solo el _id. Por ejemplo la consulta siguiente buscará elementos de tipo “RAM”, pero al no existir ninguno insertará un nuevo documento.

Ligado al parámetro upsert tenemos el modificador $setOnInsert. Este modificador sirve para realizar operaciones distintas, dependiendo de si MongoDB tiene que realizar un update o un insert. Veamos un ejemplo que incluye todas estas opciones:

Si hacemos un db.speakinbytes.find(), tendremos como resultado los siguientes documentos.

Ahora tenemos dos documentos. El primero es el que ya teníamos en nuestra colección. En este caso se ha llevado a cabo una actualización, por lo que se ignora el modificador $setOnInsert.

Con el segundo documento, el caso es el contrario. Al ser una inserción, y añadir un nuevo documento, se incluye, además de edad el campo name.

También vemos que en esta ocasión hemos cambiado el filtro de la consulta. Ahora estamos buscando todos los documentos que tengan un campo edad igual a 27. Si tuviéramos más documentos en la colección, la sentencia podría actualizar todos los que cumplen ese criterio, ya que hemos incluido el parámetro multi.

Actualización de arrays y subdocumentos con Dot Notation

Actualizar datos simples es sencillo, pero la cosa puede complicarse cuando queremos actualizar arrays con varios elementos o subdocumentos con varios campos. Vamos a insertar un nuevo producto para hacer algunas pruebas.

Una vez tenemos un documento de ejemplo, vamos a realizar algunas operaciones con él. Lo primero que vamos a probar es a modificar el array de características. Imaginemos que queremos cambiar ese “AMD socket” por “Intel Socket” con Dot Notation.

En este caso volvemos a usar $set para modificar un campo en concreto, solo que esta vez le decimos que queremos modificar el campo “caracteristicas.2″. De esta manera le estamos diciendo a MongoDB que queremos actualizar el elemento 2 del array de características. Los arrays empiezan con el índice 0, por lo que estamos modificando el tercer valor de dicho array. Si ese valor no existe, será añadido al array. Hay que tener cuidado con la posición del array utilizada, porque si añadimos un elemento a un índice cuyos valores anteriores no existen, esos valores serán rellenados con valores nulos.

Si desconocemos la posición del elemento a modificar, pero sabemos que valor tiene, podemos utilizar Dot Notation con el operador $.

Con el operador $ le decimos a MongoDB que tiene que actualizar el elemento del array que cumpla la condición de la query (caracteristicas:”Intel Socket”). Cuando usamos $ es obligatorio que el campo que lo usa (en este caso características) esté en la query. Si no es así, la Shell de MongoDB nos devolverá un error. En el ejemplo estamos cambiando el elemento caracteristicas:“Intel Socket” por “Intel Haswell Socket”.

Actualización de arrais con operadores

Si queremos añadir un valor a un campo array de un documento existente, podemos utilizar el operador $addToSet. Este operador añade el valor que le pasamos como parámetro justo al final del array. Si el campo ya existe, el valor no se añade.

Si volvemos a ejecutar el comando, veremos que no se añaden nuevos valores “WIFI”, ya que el valor ya existe en el array. Si queremos añadir varios elementos en un mismo comando, tendremos que añadir el operador $each y un array de elementos justo después del operador $addToSet.

Otra opción para añadir elementos a un array es usar el operador $push. Este comando es exactamente igual que $addToSet, con la diferencia de que, en este caso, el elemento siempre se añade, exista o no.

Si lo que queremos es eliminar elementos de un array, también tenemos distintas opciones. Una de ellas es el operador $pop, que elimina el primer o último elemento de un array.

Si al operador $pop le pasamos un 1, borrará el último elemento del array. Si le pasamos un -1, eliminará el primer elemento del array. Pero ¿y si queremos borrar un elemento en concreto sin saber su posición?. Para eso utilizaremos el operador $pull.

Si lo que necesitamos es borrar varios elementos de un array, realizaremos una acción similar, pero utilizando el comando $pullAll.

Otros modificadores útiles

Además de $set y $unset, tenemos otros modificadores que nos pueden ayudar mucho a la hora de actualizar datos. Por ejemplo tenemos el modificador $inc, que suma o resta un valor a un campo numérico.

Por ejemplo para incrementar en 5 el campo edad usaríamos el siguiente comando:

Otro operador que podemos utilizar es $rename, que sirve para cambiar el nombre de un campo existente. Por ejemplo, lo podemos utilizar sobre nuestro documento de ejemplo de la siguiente manera:

¿Cómo elimino datos?

Para eliminar datos de nuestra colección, utilizaremos el comando remove. Este comando recibe como parámetro la consulta que se utilizará para filtrar los documentos que se borrarán. Si no especificamos ninguna consulta, se eliminarán todos los datos de la colección. Como podéis ver el comportamiento es muy similar al de una operación DELETE de una base de datos relacional. Si no especificamos un filtro con la sentencia WHERE se borrarán todos los datos de la tabla.

El comando remove acepta un segundo parámetro de tipo booleano llamado justOne. Por defecto es false, pero si lo establecemos como true, solo se borrará un documento de la colección, aunque existan varios que cumplan las condiciones especificadas en la consulta.

¿Y si lo que quiero es eliminar un campo de un documento y no todo el documento?

Para , eliminar un campo del documento, utilizaríamos el comando $unset. Por ejemplo, para eliminar el campo username utilizaríamos {“$unset”:{“username”:””}}.

Operaciones de consulta avanzadas

Utilizaremos muchos de los operadores existentes en MongoDB. Podéis encontrar una lista detallada aquí.

Operaciones de comparación

¿Cómo realizamos en MongoDB el filtro si X > Y ?

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).

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.

Veamos gran parte de estos operadores:

  • Si queremos utilizar el filtro >= usaremos $gte (“greater than equals” en inglés).
  • Lo mismo haremos si queremos obtener las personas menores de 30 años utilizando los comandos $lt (“lower than”) o $lte (“lower than equals”).

  • Y si quisieramos extraer todas las personas cuya edad NO es 30 utilzaríamos el operador $ne.

  • 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.

Pero… ¿Y si queremos comparar 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. 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.

Buscando la  existencia de campos

Como ya sabéis en MongoDB no hay esquemas, 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. Por ejemplo, vamos a buscar los documentos en los que exista el campo married:

Si quisieramos buscar los documentos que no tienen el campo company, bastará con cambiar el true por un false en el $exists.

Buscando por tipo de los elementos

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.

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 cláusula 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.

¿Qué ocurre con el operador AND? Pues curiosamente que ya lo hemos utilizado sin darnos cuenta.

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, ¿Para qué podemos utilizar el operador $and? 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.

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:

$and $or se pueden usar y se usan muchas veces de manera conjunta.

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.

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. 

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.

Consulta de arrays

Los elementos que guarda un array pueden ser de cualquier tipo, es decir que pueden ser strings, números, otros arrays o incluso subdocumentos.

Imaginemos unos casos de prueba ficticios, dónde 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:

En este caso MongoDB buscará el elemento “footballer” 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:

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 “footballer” y “married” 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.

Veamos otros operadores:

  • 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 utilizando en lugar de $all, $in
  • 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

  • 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: db.people.find({tags:{$size:3}}) 
  • Otro operador interesante para proyecciones es $slice. Con este operador lo que hacemos es devolver un número determinado de elementos de un array.

Dot Notation

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:

En el ejemplo buscamos todos los documentos que cumplan la condición de que el valor 1 del array sea “footballer”Dos cosas importantes, 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

Supongamos que 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 “Pablo Ojeda” utilizaríamos una consulta como esta:

En este caso solo se devuelve los documentos que en el array del campo friends tienen el subdocumento {id:1,name:”Pablo Ojeda”}. Para buscar por un campo del subdocumento en concreto deberemos usar Dot Notation.

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”:”Pablo Ojeda” independientemente del id que tengan.

Usando Dot Notation, podemos hacer consultas más precisas y complejas:

Buscamos en el array friends los elementos que estén en la posición 2 y cuyo nombre sea mayor o igual que P. 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. 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:

En este caso buscamos todos los documentos cuyo nombre termine con la letra p mínuscula.

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”}}).sort({name : 1})  o de forma descendente db.people.find({ “friends.2.name” {$gte “T”}}).sort({name : -1})
  • Limit. Limita el número de resultados devuelto.
  • Skip. Ignora los N primeros documentos especificados. 
  • toArray. Guarda los resultados en un array que podemos asignar a una variable.

Todos estos comandos se pueden concatenar.

Y con esto, noob, ya conoces a MongoDB

 


3 Comments

So, what do you think ?