MongoDB para Principiantes

Este post pretende ser una introducción a MongoDB (MongoDB para principiantes), su modelo de datos, sus comandos de shell y sus consultas.

Últimamente he venido interesándome por el mundo de las bases de datos noSQL y más concretamente sobre MongoDB, que es una de las más utilizadas en entornos de producción, y en empresas como Foursquare y Codecademy. Este post pretende ser una introducción a MongoDB (MongoDB para principiantes), su modelo de datos, sus comandos de shell y sus consultas.

MongoDB (del inglés “humongous”, enorme) es una base de datos NoSQL, que en vez de guardar los datos en tablas, como en las bases de datos relacionales, se almacenan en estructuras de datos JSON.

Comenzó a desarrollarse en octubre de 2007 por la compañía 10gen. En 2009 se lanzó como producto independiente publicado con licencia de código abierto. Actualmente hay versiones para Windows, Linux, OSX y Solaris.

DataModel

Una instalación de MongoDB alberga una o varias bases de datos,y cada una de ellas un conjunto de coleccciones. A su vez cada colección alberga uno o varios (pudiendo ser un número enorme de ellos) documentos. Un documento es cada registro existente en una colección y la unidad básica de datos en MongoDB. Son análogos a objetos JSON pero en base de datos se almacenan en un formato más rico conocido como BSON (Binary JSON). La estructura de un documento se compone de pares “clave-valor“, separadas por “:”:

{
   field1: value1,
   field2: value2,
   field3: value3,
   ...
   fieldN: valueN
}

Cada valor, a su vez, puede ser otro documento. A continuación se muestra un ejemplo:

{
    "_id": ObjectId("4efa8d2b7d284dad101e4bc7"),
    "Last Name": "Johnson",
    "First Name": "Michael",
    "Age": 29,
    "Address": {
        "Street": "1 chemin des Loges",
        "City": "VERSAILLES"
    }
}

El campo “_id” es único en la colección. Si no lo incluimos en el documento a la hora de insertarlo en la colección, MongoDB asignará uno por defecto.

Los documentos utilizan un esquema dinámico. Esto quiere decir que los documentos dentro de una misma colección no necesitan tener los mismos campos ni estructura, y los campos comunes pueden tener distintos tipos de datos. Esta flexibilidad permite elegir el modelado de datos que más se adapte a la aplicación y a sus requisitos de rendimiento. El siguiente documento podría almacenarse junto al anterior ejemplo en la misma colección:

{
    "_id": ObjectId("d2b7d24efa884de4bc7ad101"),
    "Last Name": "Allison",
    "First Name": "Robert",
    "Hobbies": ["music", "sports"]
}

Instalación

Lo primero es descargar el software desde la página correspondiente. Existen versiones para los sistemas operativos más utilizados (Windows, Linux, OSX y Solaris) en versiones de 32 y 64 bits. La versión de 32 bits tiene el problema de estar limitada a 2Gb de datos entre todas las colecciones que almacene. Tras descargarnos el fichero zip o tgz, lo descomprimimos en el sistema de ficheros. En la carpeta principal de la instalación existirá una carpeta bin con las utilidades de la base de datos.

Para ejecutar MongoDB es necesario que exista en el directorio raíz (C:\ en Windows) la siguiente ruta C:\data\db como destino de los datos. En el momento de lanzar la ejecución de MongoDB podemos modificar la ubicación de esta ruta. A continuación ejecutamos desde línea de comandos

C:\mongodb\bin\mongod.exe

Este comando lanzará el proceso de MongoDB y si todo va bien aparecerá el mensaje “waiting for connections”. Para especificar otra ruta para los datos lo haremos indicando el directorio de datos tras el parámetro –dbpath:

C:\mongodb\bin\mongod.exe --dbpath d:\test\mongodb\data

También es posible ejecutar MongoDB como un servicio de Windows. Para instalarlo como servicio

Mongo Shell

Desde otra consola de comandos ejecutamos el siguiente comando:

C:\mongodb\bin\mongo.exe

Mongo shell (mongo.exe) conectará con el proceso mongod.exe que está ejecutando en la máquina local en el puerto 27017 por defecto. Para comprobar que está todo correcto ejecutamos los siguientes comandos:

db.test.save( { a: 1 } )
db.test.find()

El comando save ejecutado sobre la colección test, guarda el documento JSON pasado como parámetro a la función. Después de ejecutar el comando find(), obtenemos el único documento de la colección. A ese documento, como ya habíamos apuntado anteriormente MongoDB le añade un id único:

{"_id" : ObjectId("52fa4fe0d5ac743a52aa98e1"), "a" : 1 }

Por defecto usamos la base de datos test. Para ver las bases de datos utilizamos los siguientes comandos:

> show dbs
curso 0.203125GB
enron 1.953125GB
local 0.078125GB
test 0.203125GB
> use enron
switched to db enron

Junto al nombre de la base de datos, aparece el tamaño que ocupa en el sistema de ficheros. El comando use enron, cambia la base de datos de trabajo y sobre la que se ejecutan las operaciones de la shell. Para visualizar las colecciones de una base de datos ejecutamos el comando:

> show collections
messages
system.indexes

Hay que señalar que no existe ningún comando para crear una base de datos, pero podemos hacerlo con el comando use nombrenuevadb, e insertar un documento en una colección con save. MongoDB creará tanto la base de datos como la colección:

> use customers
switched to db customers
> db.clientes.save({"nombre":"juan"})
> show dbs
curso 0.203125GB
customers 0.203125GB
enron 1.953125GB
local 0.078125GB
test 0.203125GB
> db.clientes.find()
{ "_id" : ObjectId("531995937efc2c88b31af841"), "nombre" : "juan" }

Operaciones CRUD

Definimos operaciones CRUD como las operaciones para crear, leer, actualizar y borrar (Create, Read, Update and Delete) .

Consultas

Para realizar queries, MongoDB provee el método db.collection.find(), siendo collection el nombre de la colección a consultar:

Obtener todos los documentos de una colección

db.inventory.find()

Obtener todos los documentos de una colección que cumplen el valor para el tipo especificado {<field>:<value>}. Se puede indicar más de un valor para el campo con $in

db.inventory.find({tipo:"juguetes"})
db.inventory.find({tipo: { $in: ['comida', 'juguetes'] }})

Obtener todos los documentos de una colección que cumplen más de un valor (AND) para los tipos especificados {<field>:<value>}. Se pueden comparar valores con $lt (menor que) y $gt (mayor que).

db.inventory.find({tipo:"juguetes", precio: { $lt: 9.95 }})

Se pueden configurar queries con operadores OR.

db.inventory.find(
                   { $or: [
                            { qty: { $gt: 100 } },
                            { price: { $lt: 9.95 } }
                          ]
                   }
                 )

Y combinaciones de AND y OR.

db.inventory.find( { type: 'food', $or: [ { qty: { $gt: 100 } },
                                            { price: { $lt: 9.95 } 
                                          } ]
                   } )

Para retornar unos campos específicos en la consulta, los indicamos como segundo parámetro del método find. En el ejemplo sólo retorna los campos item y qty:

db.inventory.find( { type: 'food' }, { item: 1, qty: 1 } )

Para indicar que un campo no queremos que se retorne en la consulta en vez de un 1 asignamos un 0.

db.inventory.find( { type: 'food' }, { item: 1, qty: 1, _id:0 } )

Si queremos que devuelva todos los campos excepto los excluidos, únicamente indicamos éstos.

db.inventory.find( { type: 'food' }, { qty: 0 } )

Inserciones

Se pueden insertar documentos a través de varios métodos. El primero con la llamada al método insert() con el documento a insertar

db.inventory.insert( { _id: 10, type: "misc", item: "card", qty: 15 
} )

El segundo método es a través de la llamada al método update() con el flag upsert:true

db.inventory.update(
                     { type: "book", item : "journal" },
                     { $set : { qty: 10 } },
                     { upsert : true }
                   )

Intenta actualizar el registro Si no especificamos la clave _id, Mongo nos creará una por defecto que además es única

El tercer método es invocando al método save() con el documento a insertar. Si el documento pasado no contiene _id, mongo creará igualmente una clave única.

db.inventory.save( { type: "book", item: "notebook", qty: 40 } )

Actualizaciones

El método update() actualiza un único documento que coincide con el criterio de búsqueda pasado como primer parámetro, con los datos pasados en el documento del segundo parámetro. Podemos pasar la opción multi : true para indicar que se guarden varios.

db.inventory.update( { type : "book" }, { $inc : { qty : -1 } },
   { multi: true }
)

También se puede actualizar un documento por su clave “_id” mediante el método save()

db.inventory.save( { _id: 10, type: "misc", item: "placard"  })

Borrados

El borrado de todos los documentos se hace con remove() o drop()

db.inventory.remove()
db.inventory.drop()

Para el borrado de documentos que coinciden con un criterio de búsqueda

db.inventory.remove( { tipo: "comida" } )

Para profundizar en éste y otros temas podemos acudir al manual en línea de MongoDB o apuntarse a alguno de sus cursos gratuitos.

Sistema de Puntuación y Opiniones Cinco Estrellas con MySQL

Lo que vamos a ver en este post es una manera de modelar en MySQL un sistema de puntuación y opiniones cinco estrellas, es decir, puntuaciones entre 1 – 5.

Todos hemos visto, e incluso hemos votado o aportado alguna opinión en multitud de sitios sobre algún producto, alguna app, hotel o restaurante, o alguna canción/álbum de nuestro artista favorito. Es típico pulsar sobre 5 pequeñas estrellas para valorar en el rango 1 a 5 (1 = malo, 5 = excelente), de manera que la contabilización de las votaciones de todos los usuarios nos dará una puntuación media de lo que opina la gente.

Opiniones en Google Play para Angry BirdsLo que vamos a ver en este post es una manera de modelar en base de datos un sistema de votaciones y opiniones, de manera que el código a escribir en la parte servidora (PHP, RoR, Java) sea el menor posible. Imaginemos un sistema en el que tenemos una colección de imágenes. Los usuarios autenticados que visitan la exposición virtual de dichas imágenes pueden valorar de 1 – 5 cada una de ellas. Opcionalmente pueden comentar algo sobre cada una, y para todas ellas se muestra la votación media así como el total de votos emitidos. Este ejemplo se ha desarrollado con MySQL.

En base de datos tendremos una tabla que llamaremos image para almacenar todas las imágenes, identificadas con un id único (image_id). Además almacenaremos su número de votos (num_votes), la puntuación total (total_score) y la puntuación media (rating), es decir, la puntuación total / número de votos:

drop table if exists image;
create table image
(
image_id int unsigned not null auto_increment primary key,
caption varchar(255) not null,
num_votes int unsigned not null default 0,
total_score int unsigned not null default 0,
rating decimal(8,2) not null default 0
)
engine = innodb;

Además, crearemos una tabla donde almacenaremos todas las votaciones y valoraciones de los usuarios. Para cada voto almacenaremos el id del usuario (user_id), el id de la imagen (image_id) y la puntuación personal de 1 a 5. Como opción podemos añadir una opinión (review) que explique su votación.

drop table if exists image_vote;
create table image_vote
(
image_id int unsigned not null,
user_id int unsigned not null,
score tinyint unsigned not null default 0,
review varchar(4000) null,
primary key (image_id, user_id)
) 
engine = innodb; 

Por último necesitamos que los campos num_votes, total_score y rating de la primera tabla (image) se actualicen cada vez que se reciba un voto y se almacene dentro de la tabla image_vote. Para ello generamos un trigger en BBDD que se ejecute tras una inserción en la tabla image_vote, aumentando en uno el número de votos, sumando la puntuación otorgada a la puntuación total, y finalmente recalculando la media.

create trigger image_vote_after_ins_trig after insert on image_vote
for each row
begin
 update image set 
    num_votes = num_votes + 1,
    total_score = total_score + new.score,
    rating = total_score / num_votes  
 where 
    image_id = new.image_id;
end

Con este modelo que hemos creado en base de datos, el procedimiento funcionará de la siguiente manera:

  1. El sistema recogerá la votación del usuario para la imagen visualizada, junto con sus comentarios.
  2. Se almacenará la puntuación para la imagen mostrada y el usuario autenticado en la tabla image_vote.
  3. Automáticamente se actualizará el registro de la imagen seleccionada en la tabla image. El número de votos se incrementará en uno, la puntuación total se incrementará con la puntuación otorgada. Asimismo se calculará la nueva puntuación media.
  4. Al consultar la imagen de nuevo mostraremos la nueva puntuación media junto con el número total de votos.

Me gusta esta solución por su sencillez, minimizando el código que tenemos que escribir en la parte servidora, delegando toda la lógica en la capa de Base de Datos. Se puede completar mostrando las opiniones de los usuarios, junto con la puntuación que aportaron, e incluso el número de votos agrupados por los distintos valores (1 – 5).

Cuándo Adquirir una Licencia Comercial de MySQL

MySQL tiene licencia dual. ¿Qué quiere decir esto? Por una parte es libre, y por otra es comercial. Puedes obtener el software de MySQL bajo licencia GPL (v.2) o puedes comprar una licencia. Está muy extendida la sensación de que si ganas dinero con MySQL hay que comprar una licencia comercial.

A raíz de la publicación del post sobre la instalación de un servidor LAMP en Ubuntu, apareció un comentario en el mismo en el que el autor me solicitaba información acerca de si es obligatorio comprar una licencia comercial de MySQL al instalar en un cliente el sistema Ubuntu junto con este servidor.

MySQL tiene licencia dual. ¿Qué quiere decir esto? Por una parte es libre, y por otra es comercial. Puedes obtener el software de MySQL bajo licencia GPL (v.2) o puedes comprar una licencia. Está muy extendida la sensación de que si ganas dinero con MySQL hay que comprar una licencia comercial. Esto no es correcto del todo.

MySQL se distribuye bajo la licencia GPL, no es una licencia fácil de entender, por esto mismo MySQL confunde a muchos desarrolladores. ¿Hay que pagar para poder vender un producto que funciona con MySQL? Únicamente se requiere una licencia comercial en estos dos escenarios:

  1. Si queremos modificar el código de MySQL y queremos redistribuir estas modificaciones cobrando por ellas.
  2. Si queremos incluir MySQL dentro de nuestro software como uno solo. Hay que tener en cuenta que no significa que no podamos conectarnos con MySQL, si no que no podemos incluirlo en nuestro software. Por ejemplo, WordPress trabaja con PHP y se conecta a MySQL, pero no lo distribuye.

Muy importante. Si quieres incluir MySQL dentro de tu software y distribuirlo conjuntamente y no deseas comprar una licencia comercial, puedes hacerlo, pero tu software deberá estar licenciado de la misma forma, acogiéndote a la licencia GPL y liberar el código.

O sea, para cualquier otra cosa, no necesitamos una licencia comercial. Las siguientes cosas están permitidas bajo la licencia GPL:

  • Gestionar un negocio que te reporte beneficios usando MySQL
  • Modificar su código fuente como se desee.
  • Vender y distribuir MySQL (hay que hacerlo bajo licencia GPL también).
  • Redistribuir las modificaciones realizadas.

Otros puntos importantes que la licencia no obliga:

  • No requiere redistribuir las modificaciones realizadas a MySQL
  • No requiere de una licencia GPL del software que se conecta a MySQL
  • No obliga a que todo el software en su empresa también sea GPL

Para aclarar las ideas acerca de la licencia GPL, puedes consultar las FAQ de la licencia. Cómo observaréis todo software libre puede no ser gratis, dependiendo de la licencia bajo la que se acoja. El motor de base de datos PostgreSQL, por ejemplo, es otra alternativa para entornos de producción que se distribuye bajo su propia licencia, muy similar a la licencia MIT y mucho menos restrictiva que la GPL.