By

Empezando con MongoDB Part.II

En mi anterior post hablaba sobre cómo empezar con MongoDB, una base de datos NoSQL orientada a documentos; y como sigo estudiándola y me parece interesante, me gustaría seguir compartiendo y profundizando sobre el uso de MongoDB, así que he decidido escribir una segunda parte de este anterior post.

El objetivo de este post es bien sencillo. Cubrirá las operaciones principales básicas de MongoDB, también conocidas como operaciones CRUD, que significa C reate, R ead, U pdate y D elete. ¡Así que empecemos con ello!

Antes de nada empezaremos recordando cómo iniciar nuestra base de datos utilizando el comando mongod. Recordad que debemos especificar la ruta donde se almacenará la base de datos utilizando el parámetro --dbpath. Dicho esto, podremos ejecutar una instrucción como la siguiente:

$ mongod --dbpath test

En este caso he utilizado un directorio llamado test donde alojaré esta base de datos de pruebas. Apuntar que al no especificar un puerto con el parámetro --port, la instancia de mongod utilizará el puerto 27017 por defecto.

Esta instrucción tiene un “inconveniente” y es que no podremos seguir utilizando nuestra consola actual, pues la ejecución de mongod se realiza en primer plano, o foreground. Para evitar esto podemos hacer uso del parámetro --fork que creará un fork del proceso del servidor permitiendo poder seguir utilizando nuestra consola. La opción --fork requiere que se especifique además un fichero de log utilizando el parámetro --logpath o --syslog, así que la instrucción quedaría de la siguiente forma:

$ mongod --dbpath test --logappend --logpath test.log --fork
forked process: 10017
all output going to: /home/mviera/Downloads/mongodb/test.log
child process started successfully, parent exiting

Nota: Además he utilizado la opción --logappend para evitar que sobrescriba el contenido del fichero de log. En este caso seguirá concatenando datos al contenido actual del fichero.

Una vez funcionando nuestra instancia de mongod, podemos conectarnos a la consola utilizando el comando mongo de la siguiente forma:

$ mongo
MongoDB shell version: 2.2.3
connecting to: test
>

Nota: No es necesario especificar el puerto porque se está utilizando el puerto por defecto (27017).

Todo listo, así que ¡empecemos a crear documentos!

Introduciendo contenido

Seguro que muchos ya os imagináis cómo se realiza este tipo de operación básica en una base de datos SQL como MySQL, PosgreSQL, SQLite, etc; pero en MongoDB se realiza de una forma muy diferente, por lo menos referente a cómo se realiza en una base de datos SQL.

Una inserción de datos sencilla en SQL podría ser de la siguiente forma:

INSERT INTO users (firstName, lastName, username, age, city)
VALUES ('Manuel', 'Viera', 'mviera', 26, 'Sevilla');

En MongoDB es completamente diferente. En mi anterior post ya traté rápidamente las inserciones y si recordáis, comentaba que MongoDB está orientado a documentos y que estos documentos siguen el estilo JSON (JavaScript Object Notation).

Para realizar una inserción en MongoDB utilizaremos el método insert() al que llamaremos pasándole como parámetro el documento a introducir, el cual, siguiendo el ejemplo anterior, sería representado en formato JSON de la siguiente forma:

{
  "firstName" : "Manuel",
  "lastName" : "Viera",
  "username" : "mviera",
  "age" : 26,
  "city" : "Sevilla"
}

Recordad también que en MongoDB los documentos se almacenan en colecciones (o collections en inglés), así que para seguir con el ejemplo introduciremos este documento en una colección llamada users.

> db.users.insert({
 firstName: "Manuel",
 lastName: "Viera",
 username: "mviera",
 age: 26,
 city: "Sevilla"
 })

Bien. ¿Qué tenemos hasta ahora? Por defecto cuando nos conectamos a una instancia MongoDB sin especificar una base de datos, se utilizará por defecto test. Así que hemos creado un documento dentro de la colección users en la base de datos test.

Nota: A modo de recordatorio, no es necesario crear previamente la colección antes de introducir documentos. MongoDB lo hará por nosotros automáticamente si la colección no existe.

¿Pero cómo recuperamos los datos introducidos?

Recuperando los documentos

Para seleccionar, buscar, encontrar documentos dentro de nuestra colección utilizaremos uno de los siguientes métodos:

  • find() : que devolverá un cursor con todos los documentos recuperados de la base de datos.
  • findOne() : que devuelve solamente un documento.

El uso de find() es muy sencillo, como podéis ver a continuación:

> db.users.find()
{ "_id" : ObjectId("5131f87b350e650534c68d8e"), "firstName" : "Manuel", "lastName" : "Viera", "username" : "mviera", "age" : 26, "city" : "Sevilla" }
>

En este caso tanto find() como findOne() devolverán resultados idénticos, pues ahora mismo sólo contamos con un documento en nuestra colección users:

> db.users.findOne()
{
        "_id" : ObjectId("5131f87b350e650534c68d8e"),
        "firstName" : "Manuel",
        "lastName" : "Viera",
        "username" : "mviera",
        "age" : 26,
        "city" : "Sevilla"
}
>

Si os fijáis en un detalle, findOne() devuelve el contenido de una forma mucho más legible al ojo humano que find(). Esto también podemos conseguirlo con find() si utilizamos además el método pretty() de la siguiente forma:

> db.users.find().pretty()
{
        "_id" : ObjectId("5131f87b350e650534c68d8e"),
        "firstName" : "Manuel",
        "lastName" : "Viera",
        "username" : "mviera",
        "age" : 26,
        "city" : "Sevilla"
}

Al igual que pretty() también podemos utilizar otros métodos como count() que nos devuelve la cantidad total de documentos devueltos:

> db.users.find().count()
1
>

O directamente sobre la colección `users` para saber el total de documentos de dicha colección:

> db.users.count()
5
>

Si introducimos más documentos de prueba, se aprecia que el contador total de documentos cambia:

> db.users.insert({firstName:"Paco", lastName:"Laverdaque", username: "laverdaque", age: 40, city: "Madrid"})
> db.users.insert({firstName:"Raul", lastName:"Martin", username: "rmartin", age: 39, city: "Malaga"})
> db.users.insert({firstName:"Jose", lastName:"Castillo", username: "lolo", age: 25, city: "Cadiz"})
> db.users.insert({firstName:"Jose Luis", lastName:"Romero", username: "selu", age: 27, city: "Barcelona"})
> 
> db.users.count()
5

Pero no siempre vamos a querer recuperar todos los documentos de nuestra colección…

¿Cómo filtrar documentos?

Seguro que muchos ya estáis pensando en la cláusula WHERE de las sentencias SQL. Por ejemplo, si quisiéramos recuperar aquellos usuarios cuya ciudad es Sevilla, con una sentencia SQL sería:

> SELECT * FROM users WHERE city="Sevilla";

Sin embargo, en MongoDB es de la siguiente forma:

> db.users.find({ city: "Sevilla" })

De esta forma, pasamos como parámetro un documento JSON con la cláusula a cumplir, en este caso que la clave city tenga como valor Sevilla.

¿Y cómo podemos concatenar cláusulas? Es decir, cómo sería si quisiéramos recuperar aquellos usuarios cuya ciudad es Sevilla y además su edad sea 26:

> db.users.find({ city: "Sevilla", age: 26 })

Como se puede observar, solamente se necesita agregar una segunda clave o campo al documento JSON que se le pasa al método find().

Nota: MongoDB aplica por defecto un AND entre cláusulas, es decir, imaginaos que la coma (,) es un operador AND.

Para utilizar un operador OR deberemos usar el operador $or de MongoDB, de la siguiente forma:

> db.users.find({ $or: [ {city:"Sevilla"}, {city:"Malaga"} ] })

En este caso hemos recuperado aquellos usuarios cuya ciudad es Sevilla o Málaga.

Podéis aprender más sobre operadores en http://docs.mongodb.org/manual/reference/operators/. Algunos de ellos son:

  • $gt: mayor que (o greater than en inglés).
  • `$lt: menor que (o lower than en inglés).
  • $gte: mayor o igual que (o greather or equal than en inglés).
  • $lte: menor o igual que (o lower or equal than en inglés).
  • $not: no (negación o not en inglés).
  • $in: en, para buscar dentro de un array.
  • $nin: no en, para buscar algo que no se encuentre en un determinado array (o not in en inglés).
  • $ne: no es igual a (o not equal to en inglés).

Pero, no siempre vamos a querer recuperar todos los campos de un documento, quizás solo necesitemos un algunos de ellos…

¿Cómo seleccionar ciertos campos?

Al igual que en una sentencia SELECT, en una base de datos SQL, podemos especificar qué determinados campos queremos obtener tras seleccionar los documentos, en MongoDB también podemos hacerlo.

Hasta ahora hemos estado utilizando find() como una sentencia SQL SELECT *, es decir:

> db.users.find()

es equivalente a

> SELECT * FROM users;

Pero ¿y si lo que queremos es solamente recuperar los campos username y age? En una sentencia SQL sería de la siguiente forma:

> SELECT username, age, city FROM users;

En cambio, en MongoDB sería de la siguiente forma:

> db.users.find({}, {username:1, age:1})
{ "_id" : ObjectId("5131f87b350e650534c68d8e"), "username" : "mviera", "age" : 26 }
{ "_id" : ObjectId("5131fdc8350e650534c68d8f"), "username" : "laverdaque", "age" : 40 }
{ "_id" : ObjectId("5131fe00350e650534c68d90"), "username" : "rmartin", "age" : 39 }
{ "_id" : ObjectId("5131fe20350e650534c68d91"), "username" : "lolo", "age" : 25 }
{ "_id" : ObjectId("5131fe80350e650534c68d92"), "username" : "selu", "age" : 27 }

Sin embargo, como podéis observar, aunque no hemos especificado el campo _id sigue apareciendo en la salida. Como ya comenté en el anterior post, MongoDB siempre muestra el campo _id a menos que especifiquemos explicitamente que no queramos que lo haga. Y sería de la siguiente forma:

> db.users.find( {}, { username:1, age:1, _id:0 })
{ "username" : "mviera", "age" : 26 }
{ "username" : "laverdaque", "age" : 40 }
{ "username" : "rmartin", "age" : 39 }
{ "username" : "lolo", "age" : 25 }
{ "username" : "selu", "age" : 27 }

En resumen, si analizamos la sintaxis del método find(), primero se especifica un documento JSON con las cláusulas del filtro que queremos aplicar y a continuación, si lo preferimos, podemos activar o desactivar cierto campos de la salida:

  • 1 : indica que queremos que muestre el campo especificado.
  • 0 : indica que no queremos que dicho campo se muestre en la salida.

Actualizando datos de los documentos

Una vez que somos capaces de introducir documentos en nuestra colección, recuperarlos todos o aquellos que nos interesan utilizando una query es hora de conocer cómo podemos actualizar los datos de nuestros documentos.

Para ello utilizaremos el método update() cuya sintaxis es la siguiente:

> db.collection.update( <query>, <update>, options )
>
  • El parámetro <query> hace referencia a una query como las que usabamos con el comando find(), la cual nos permitirá seleccionar aquellos documentos los cuales queremos modificar. De otra forma, si no se especifica una query se estarían seleccionando todos los documentos de una colección.
  • El parámetro <update> en el que podemos especificar un documento completo, que actualizará cada uno de los campos del documento seleccionado; o también podemos especificar solamente el campo del documento que queremos especificar, lo cual es más eficiente comparado con actualizar todos los campos del documento.
  • Y con respecto a las opciones podemos citar:
  • upsert: si se establece este parámetro a true y no hay documento que coincida con la <query> especificada, el método update() insertará un nuevo documento en la colección con los valores especificados en el parámetro <update>.
  • multi : por defecto MongoDB solo actualiza un documento al mismo tiempo, si necesitamos actualizar más de un documento al mismo tiempo debemos establecer este parámetro a true.

Actualizar un documento completo

Esta no es la forma más eficiente de actualizar el contenido de un documento, como comenté anteriormente, pues es necesario que se especifique el contenido completo del documento, y en caso de ser un documento muy grande puede llegar a ser bastante ineficiente, ya que tendra que actualizarse el valor de cada campo aunque no hay cambiado.

Imaginemos que queremos modificar un documento para cambiar solamente el valor del campo age (edad). Tendríamos que realizar lo siguiente:

> db.users.update({ username: "rmartin" }, { "firstName" : "Raul", "lastName" : "Martin", "username" : "rmartin", "age" : 40, "city" : "Malaga" } })

Con esta instrucción hemos cambiado el valor del campo age de 39 a 40, incluyendo los demás campos que serán actualizados con los mismos datos.

También es posible hacerlo de la siguiente forma para nuestra comodidad:

> user = db.users.findOne({ username:"rmartin" })
{
        "_id" : ObjectId("5131fe00350e650534c68d90"),
        "firstName" : "Raul",
        "lastName" : "Martin",
        "username" : "rmartin",
        "age" : 39,
        "city" : "Malaga"
}
> user.age 
39
> 
> user.age = 40
40
> 
> db.users.update({ username:"rmartin" }, user )
  1. Recupero el documento completo referente al usuario cuyo username es rmartin y lo almaceno en la variable user.
  2. Compruebo el valor actual del campo age accediendo al objeto user haciendo uso del dot notation (notación por punto: user.campo)
  3. Modifico el valor del campo age estableciéndolo a 40.
  4. Utilizo el método update() seleccionando de nuevo el mismo objeto que al principio (usando la misma query) e indico el nuevo documento a continuación, en este caso, almacenado en la variable user.

Actualizando un campo concreto de un documento

Sin embargo, existe una alternativa mucho más eficiente para actualizar el valor de ciertos campos en un documento. Para ello será necesario volver a hacer uso de los operadores como los anteriormente citados. Algunos de estos son:

  • $set: que permite establecer un nuevo valor a un campo del documento. Si el campo no existe actualmente en el documento, será creado automáticamente. Por ejemplo:

    > db.users.update({ age: 26, city: "Sevilla" }, { $set: { weight: 68.9 } })
    

En este caso se ha seleccionado un usuario cuya edad sea 26 y su ciudad Sevilla, y se ha añadido el campo weight.

  • $addToSet: para añadir elementos un array. Por ejemplo, si quisieramos añadir un nuevo campo llamado likes con los gustos de un usuario:

    > db.users.update({ username:"mviera" }, {$addToSet: { likes: "photography" }})
    > db.users.update({ username:"mviera" }, {$addToSet: { likes: "music" }})
    >
    > db.users.findOne({ username: "mviera" })
    {
        "_id" : ObjectId("5131f87b350e650534c68d8e"),
        "age" : 26,
        "city" : "Sevilla",
        "firstName" : "Manuel",
        "lastName" : "Viera",
        "likes" : [
                "photography",
                "music"
        ],
        "username" : "mviera",
        "weight" : 68.9
    }
    >
    
  • $inc: permite incrementar el valor numérico de uno de los campos del documento. Por ejemplo, si quisieramos incrementar en 5 la edad del usuario mviera:

    > db.users.update({ username:"mviera" }, {$inc: {age: 5}})
    > db.users.find({ username: "mviera" }, {username: 1, age: 1, _id:0})
    

    { “age” : 31, “username” : “mviera” }

Para más información acerca de los operadores utilzados en el método update() podéis consultar http://docs.mongodb.org/manual/applications/update/#update-operators

Eliminando documentos

Para eliminar documentos de nuestra colección utilizaremos el método remove() en la shell de MongoDB. Si ya sabes cómo utilizar el método find() no supondrá ningún problema porque solamente requiere que se le especifique el filtro o búsqueda de los documentos que se quieren eliminar. Su sintaxis es la siguiente:

db.collection.remove( <query>, <justOne> )

De forma que si queremos eliminar de la colección todos los usuarios cuya edad sea mayor a 30 utilizaríamos la siguiente instrucción:

> db.users.remove({ age: { $gt: 30 } })

El parámetro justOne permite indicar al método remove() si queremos eliminar solamente un documento de todos los coincidentes. En ese caso, se debe pasar el valor true o 1, de la siguiente forma:

> db.users.remove({ age: { $gt: 30 } }, true )

Es posible utilizar el método remove() para eliminar todos los documentos de una colección si no se especifica una query o si ésta está vacía, es decir, db.users.remove({}). Pero en este caso, si queremos eliminar todos los documentos de una colección, es recomendable utilizar el método drop(), que elimina la colección completa:

> db.users.drop()

¡Y eso es todo! Espero que os haya sido útil y por lo menos hayáis aprendido algo nuevo. Intentaré seguir escribiendo sobre MongoDB, ahora que ya conocemos lo básico, lo mejor está por llegar: ReplicaSets, Indexes, Shardings, etc.

¡No olvidéis comentar vuestras impresiones y correcciones!

Un saludo, Manu.