Code School Free Weekend 2016 – MongoDB

Empezamos con el primero de los cursos que voy a tragarme durante el Code School Free Weekend, el de MongoDB.

Antes de empezar

De los cursos que quiero hacer, he decidido empezar por este precisamente por ser un tema el tema del que menos idea tengo. He oído mucho de Mongo pero nunca he tenido el tiempo ni las ganas de meterle mano. Lo que sé:

  1. Es una base de datos NoSQL (y yo llevo media vida con mySQL, SQL server y Oracle)
  2. Lee las consultas y devuelve los resultados como objetos de javascript
  3. Está diseñado para escalar (replicación, indexación, balanceo y demás)

Apuntes

Comparando MondoDB con una base de datos tradicional, el equivalente a las bases de datos, tablas y registros son las bases de datos, colecciones y documentos.

En mongo, la estructura de un documento no es fija, conmo en un registro de SQL, diferentes documentos de una colección pueden tener diferentes campos.

Los documentos de una colección son objetos de javascript serializados en BSON Una extension de JSON) y los métodos para interactuar con ellos son funciones de javascript.

help devuelve una lista de los comandos disponibles
use myApp cambia a la base de datos myApp, si no existe la crea, db devuelve la base de datos actual
db.users.insert({«name»:»Nilo»}) crea un documento en la colección users, en la base de datos actual. Si la colección no existe, la crea.
db.users.find() devuelve los documentos de una colección
db.users.find({«name»:»Nilo»}) devuelve los documentos cuyo campo name sea igual a Nilo (o que contenga el elemento Nilo, si name es un array)
Mongo autogenera un campo _id de tipo ObjectId con un hash único para cada documento, aunque se puede forzar a cualquier valor único.

Los campos de un documento pueden ser de todos los tipo de datos válidos en JSON (string, number, boolean, array, object y Null) y además los específicos de BSON: ObjectId e ISODate

Cuando se guarda un valor como new Date(2012, 8, 13) (los meses de javascript empiezan en 0), mongo lo almacena como ISODate(«2012-09-13T04:00:00Z»)

Mongo no valida el tipo de los valores que metemos en un campo, hay que validar el contenido antes de guardarlo.

db.users.remove({«name»:»Nilo»}) borra todos los documentos cuyo campo name sea Nilo (o contenga el elemento Nilo, si name es un array)
db.users.remove({}) borra todos los documentos de la colección users (es el equivalente a TRUNCATE `users` en SQL)
db.users.update({«name»:»Nilo»},{«$set»: {«age», 35}})
aplica el operador de actualización $set (sólo cambia los campos especificados) sobre el primer documento que cumpla las condiciones del primer parámetro de update()
db.users.update({«name»:»Nilo»},{«age», 35}) como no tiene el operador $set, reemplaza todo el primer documento que encuentre por el objeto del segundo parámetro de update()
db.users.update({«age»:»35″},{«$set»: {«cool», true}}, {«multi»: true})
el tercer de parámetro de update() son las opciones, multi hace que no sólo actue sobre todos los objetos que encuentre
db.logs.update({«name»:»Nilo»},{«$inc»:{«views», 1}})  usa el operador $inc para incrementar el valor del campo age («$inc»:{«views», -2} le restaría 2), si el campo no existe, lo crea con el valor especificado
db.logs.update({«name»:»Nilo»},{«$inc»:{«views», 1}}, {«upsert»: true}) la opción upsert permite que update cree un docuemtno si no encuentra ninguno que concuerde con la el primer parámetro
db.users({},{«$unset»: {«age»: «»}},{«multi»:true}) borra el campo age de todos los documentos de la colección
db.users({},{«$rename»: {«age»: «edad»}},{«multi»:true}) renombra el campo age a edad en todos los documentos de la colección

Hay muchos operadores de actualización (update operators), la documentación oficial está aquí.

db.products.find({«price»: {«$lt»: 20, «$gt»: 10}}) devuelve los documentos en los que el valor del campo price sea menor que (less than) 20 y mayor que 10 (greater than)

Hay muchos operadores de consulta (query operators), la documentación oficial está aquí.

db.products.find({«price»: {«$lt»: 20}}, {«vendor»: true, «name»: true}) el segundo parámetro de find() son las proyecciones. En este caso, va a devolver los documentos que coincidan, pero sólo va a mostrar los campos _id, vendor y name. O especificas todos los campos que quieres (con true), o todos los que no quieres (con false), pero no puedes mezcla inclusiones con exclusiones

db.products.find({«price»: {«$lt»: 20}}, {«vendor»: true, «name»: true, «_id»: false}) este es el úncio caso en el que se pueden mezclar inclusiones y exclusiones, cuando se especifican las inclusiones pero se quiere quitar _id

Agregar resultados

db.products.aggregate([{«$group» : {«_id»: «$vendor_id»}}]) devuelve datos agrupados _id es la clave del grupo, es el id que se le va a asignar al grupo.
El equivalente en SQL sería SELECT DISTINCT `vendor_id` AS _id FROM `products`

db.products.aggregate([{«$group» : {«_id»: «$vendor_id», «total»: {«$sum»: 1}}}]) suma 1 por cada elemento del grupo (es lo mismo que contarlos)
En equivalente en SQL sería SELECT `vendor_id` as _id, COUNT(*) as total FROM `products` GROUP BY `vendor_id`

db.products.aggregate([{«$group» : {«_id»: «$vendor_id», «total»: {«$total»: «$price»}}}]) hace un sumatorio el campo total del valor del campo price de todos los documentos del grupo

aggrecate() funciona como un pipe con estados definidos en un array, cada estado pasa sus resultados al siguiente

db.potions.agregate([
  {«$match»: {«price»: {«$lt»:15}}},  // $match funciona como find(), restringe los documentos devueltos
  {«$project»: {«_id»: false, «vendor_id»:true, «grade»: true}}, // $project funciona como las proyecciones de find()
  {«$group»: {«_id»: «$vendor_id»,»avg_grade»: {«$avg»: «$grade»}}}, // $group combina campos
  {«$sort»: {«avg_grade»: -1}}, // $sort es igual que sort()
  {«$limit»: 3} // $limit es igual que limit()
]);

Recapitulando

El curso me ha resultado mucho más fácil de lo que esperaba porque he descubierto que MongoDB está muy relacionado con muchas más cosas que conozco de lo que esperaba:

  1. La interfaz es javascript y los comandos se formatean como JSON, así que hacer consultas es como hablar con un API REST
  2. la forma de concatenar consultas es prácticamente igual que la del Active Record de CodeIgniter
  3. la forma de estructurar la información es la que muchas veces terminas terniendo que emular en mySQL a base de hacer un serie interminable de consultas, hacer consultas contra MongoDB es muy parecido a atacar vistas de mySQL cuando ya se ha encargado antes alguien de montar todas las relaciones, triggers y funciones.

Lo que me falta

Ahora me queda aplicar en el mundo real lo que he aprendido y aprender cómo se realizan las tareas de administración: levantar el servicio, replicar datos, gestionar permisos…