8. Bases de Datos NoSQL

En este tema aprenderemos a:

  1. Conocer qué son las bases de datos NoSQL

  2. Usar Postgres como almacén de documentos JSON

  3. Usar MongoDB como un ejemplo de BD NoSQL

Está organizado en 8 talleres de unas 3 sesiones de clase. En total unas 24 sesiones de clase más tu trabajo en casa.

La dedicación depende del conocimiento previo, motivación y capacidad de aprendizaje del estudiante para esa sesión en concreto.

Video Clase

8.1. Sistemas Gestores de BD NoSQL

Video Clase

  1. SGBD NoSQL

    1. Not only SQL: no usan SQL (en general)

    2. No usan el modelo relacional

    3. Sistemas muy heterogéneos:

      1. Tanto el funcionamiento del Sistema gestor

      2. como el modelo de datos (almacenamiento estructurado)

    4. Rendimiento y escalabilidad muy altas pero …

    5. … a coste de funcionalidad (JOIN, soporte transaccional, integridad de datos, tipo de almacenamiento, etc)

    6. Generalmente con arquitecturas distribuidas (descentralizadas y redundantes)

  2. Muy usadas en desarrollo web porque:

    1. Importa más la velocidad que la integridad (se hace a nivel aplicación)

    2. Porque se necesita rendimiento y escalabilidad (salvajes)

    3. Se puede «delegar» la complejidad de un SGBR (microservicios con APIs) y aumentar la redundancia

    4. Se busca descentralizar la aplicación para mejorar:

      1. concurrencia

      2. escalabilidad

      3. disponibilidad

  3. Almacenamiento estruturado (no relacional y con integridad sencilla). Muchos tipos:

    1. Usando pares clave/valor. Por ejemplo arrays asociativos o pares de clave = a valor (objeto sin estructura)

    2. Usando objetos (JSON por ejemplo) y/o colecciones de objetos

    3. Orientadas a grafos: nodos de almacenamiento relacionados entre sí (similar a un modelo en red)

    4. Uso de documentos XML

  4. Sistemas Gestores de BD NoSQL (hay muchos). Estos son 5 ejemplos:

    1. Apache Casandra . Almacenamiento tablas particionadas y distribuidas.

    2. Redis . Almacenamiento hash (clave=valor), muy usado como almacenamiento temporal (RAM)

    3. MongoDB. Almacenamiento Documental (Objetos) y muy popular

    4. Apache CouchDB . Almacenamiento Documental (Objetos JSON), accesible vía API y replicable

    5. Google BigTable . Almacenamiento en tablas multidimensionales (sistema GFS), distribuido y muy eficiente

  5. Un buen resumen final (desde la perspectiva de MongoDB)

Investigación sobre BD NoSQL

Escogiendo un SGBD NoSQL la idea es responder a algunas de las preguntas genéricas de Administrar un SGBD (Cualquiera)

  1. ¿Cómo se instalaría? ¿Cual es su documentación oficial?

  2. ¿Cómo me conecto? ¿Tiene soporte para conectarme desde la red?

  3. ¿Cómo sería un ejemplo de almacenamiento? ¿Cual podría ser un modelo de datos? ¿Qué es lo que puedo almacenar?

  4. ¿Cómo consultaría los datos? ¿En qué lenguaje?

  5. ¿Cómo se te ocurriría implementar el almacén de la aplicación sobre los vuelos y los viajes del tema anterior?

Recuerda que cada SGBD NoSQL es muy específico

8.2. Usando JSON en Postgres

Video Clase

  1. Usar Postgres como SGBD Objeto Relacional:

    1. Puede almacenar documentos XML y objetos JSON

    2. Ofrece operadores y funciones para gestionarlos

    3. Usando la «lógica» SQL (lenguaje declarativo)

  2. Tipo de datos JSON (datatype-json):

    1. Basado en el estándar JSON (RFC 7159)

    2. Valores (string / number /boolean), arrays y objetos anidados

    3. Va entre comillas (un JSON se puede representar como una cadena de texto con una estructura).

    4. Es muy flexible pero ojo que mezclas esquema (estructura) y valores.

    SELECT '5'::jsonb; -- Un valor simple
    
    SELECT '[1, 2, "foo", null]'::json; -- Un array.
    -- Ojo que este null no es el NULL de SQL (que sería valor completo)
    
    SELECT '{"foo": [true, "bar"], "tags": {"a": 1, "b": null}}'::json; -- Anidados
    
  3. Usando JSON:

    1. Se usan como un tipo de datos sql más (como una cadena de texto «especial») pero …

    2. … puedes usar los operadores especiales para JSON

      1. Hay muchas opciones (functions-json)

      2. Los más sencillos sería usar

        1. -> si usamos el índice (número)

        2. ->> si usamos la clave (texto)

      -- jsonb -> integer → jsonb
      '[{"a":"foo"},{"b":"bar"},{"c":"baz"}]'::json->2
      
      jsonb -> text → jsonb
      '{"a":1,"b":2}'::json->>'b'
      
    3. Y también hay funciones especiales (functions-json)

      1. de creación. Sirven para construir objetos JSON a partir de otros datos. Por ejemplo:

        -- to_jsonb ( anyelement ) → jsonb
        to_jsonb(row(42, 'Fred said "Hi."'::text)) → {"f1": 42, "f2": "Fred said \"Hi.\""}
        
        -- jsonb_object ( text[] ) → jsonb
        jsonb_object('{{a, 1}, {b, "def"}, {c, 3.5}}') → {"a" : "1", "b" : "def", "c" : "3.5"}
        
      2. de consulta. Sirven para procesar información del objeto. Por ejemplo:

        -- jsonb_array_length ( jsonb ) → integer
        jsonb_array_length('[1,2,3,{"f1":1,"f2":[5,6]},4]') → 5
        
        -- jsonb_each ( jsonb ) → setof record ( key text, value jsonb )
        
        select * from json_each('{"a":"foo", "b":"bar"}') →
        
           key | value
           ------------
           a   | "foo"
           b   | "bar"
        
        -- jsonb_object_keys ( jsonb ) → setof text
        
        select * from json_object_keys('{"f1":"abc","f2":{"f3":"a", "f4":"b"}}') →
        
        json_object_keys
        ------------------
        f1
        f2
        

Tutorial Sencillo

Siguiendo este sencillo tutorial (o con lo ya visto), haz los siguientes ejercicios:

  1. Crea la tabla ejemplo con dos campos, uno la clave primaria y otro de tipo jsonb

  2. Añade 5 objetos JSON que representen a una persona. La estructura no tiene porqué ser la misma

  3. Consulta la tabla, por ejemplo:

    1. El nombre de una persona concreta, conociendo su dni

    2. Mostrar el nombre y apellido de todas las personas que tengas almacenadas

    3. El número de registros cuyo nombre comience por la letra M

BD en Postgres usando JSON

Diseña un almacén de datos que te permita gestionar y acceder a los datos de una aplicación de gestión de viajes sencilla. Se cumplirán los siguientes requisitos:

  1. Necesitamos conocer la información de los viajeros y los vuelos que hacen.

  2. Los viajeros son españoles o con residencia en España, y queremos saber el día que nacen para saber cual es su edad y felicitarle el cumpleaños. Un viajero puede hacer muchos viajes

  3. De los viajes queremos conocer de qué ciudad es el origen y cual es el destino, y claro la fecha (de salida y llegada). Ah! Y de cada ciudad queremos saber cuantos habitantes tiene y una descripción sencilla de 150 caracteres máximo con un recomendación de cosas para ver.

Este es un ejercicio de repaso de diseño conceptual, lógico y físico. Con esta BD trabajaremos en los siguientes clases. El resultado será un BD a la que te podrás conectar:

  1. Creas los objetos de la base de datos

  2. Le añades al menos 3 registros de ejemplo de contenido (para no tener sólo la estructura, sino también el contenido)

  3. Y así tendrás una BD a la que poder acceder desde consola o en tu aplicación

La BD a usar será postgres pero usando objetos JSON.

8.3. El SGBD MongoDB

Video Clase

  1. MongoDB es un SGBD:

    1. Orientado a Documentos: almacena colecciones de documentos en formato (casi) JSON

    2. Muy flexible, replicable, redundante y rápida

    3. De código abierto (aunque una licencia especial )

  2. Muy usada en desarrollos web por su flexibilidad

  3. Con limitaciones importantes si el modelo de datos o las necesidades de integridad son muy importantes

  4. Se puede instalar en múltiples plataformas :

    1. En tu propio equipo

    2. En su equipo (Cloud). Por ejemplo MongoDB Atlas

  5. Lo que vamos a usar:

    1. Servidor Cloud (no en local). Te das de alta (y creas tu sistema)

    2. Cliente vía consola (mongosh). Lo instalas en tu equipo.

  6. Algunos conceptos (mongodb_get_started)

    1. Almacenamiento en tablas (Relacional) vs objetos (por ejemplo documentos)

    2. Servidor en local o en la nube (Cloud)

    3. Cluster: tu servidor de BD (el SGBD y el almacenamiento para las BD)

    4. La conexión:

      1. La cadena de conexión: parámetros necesarios para conectarse

      2. El software de conexión (múltiples):

        1. Hay muchos: cliente CLI (mongosh), cliente GUI (Compass), software (drivers según lenguaje), API (aplicación web), etc

        2. Define la forma de conectarse y los métodos (diferentes según el tipo de cliente que uses). Aquí usaremos consola mongosh

Preparando tu entorno de desarrollo

Prepara tu entorno de desarrollo (Ejercicios en Ejercicios con MongoDB (en Cloud y mongosh))

8.4. El modelo de datos (y la BD)

Video Clase

  1. Creando una BD (mongodb_mongosh_create_database)

    1. Creas una BD

    2. Te conectas

    show databases;
    use mi_bd_ejemplo
    db
    
  2. Añades información

    1. Añades documentos (en realidad creas colecciones, que tienen documentos) usándo métodos (recuerda la POO):

    2. Y puedes ir añadiendo objetos (documentos) a esa colección

    db.createCollection("tu_coleccion")
    db.tu_coleccion.insertOne(object)
    
  3. Y puedes borrarlas:

    1. La colección: db.collection.drop

    2. Y la base de datos: db.dropDatabase

  4. Los documentos BSON (objetos):

    1. Como JSON pero con representación binaria (BSON)

    2. Estructura en base a pares clave: valor

    3. El valor del campo puede ser muchas cosas (es muy flexible en esto)

    4. Las claves son siempre cadenas de texto (string)

    5. Se pueden incluir documentos como valores (embedded documents)

    6. Pero OJO que una cosa es cómo lo representas (básicamente un JSON con mayor variedad de tipos) y otro como se almacena (BSON).

      // Algo básico
      {
        nombre: "yo",
        apellido: "tu",
        edad: 20
      }
      
      // Usando funciones JS o arrays como valores
      // Puede almacenar objetos binarios también como valores
      {
            _id: ObjectId("5099803df3f4948bd2f98391"),
            name: { first: "Alan", last: "Turing" },
            birth: new Date('Jun 23, 1912'),
            death: new Date('Jun 07, 1954'),
            contribs: [ "Turing machine", "Turing test", "Turingery" ],
            views : NumberLong(1250000)
      }
      
      {
        nombre: { primero: "Yo", segundo: "Pepe" },
        contacto: { telefono: { tipo: "movil", numero: "111-222-3333" } }
      }
      
  5. Se usa la notación punto (dot notation) para acceder a los elementos del documento o un valor. P.ej tu_doc.nombre (valor de la clave nombre) o tu_doc.2 (tercer elemento)

Tu primera base de datos

  1. Crea una BD que se llame ejemplo

  2. Añade tres colecciones: una, dos y tres

  3. Revisa qué BD hay en el «cluster»

  4. Revisa qué colecciones hay en la BD «ejemplo»

  5. Borra las colecciones y la BD

Base de datos Viajes en MongoDB

Vamos a usar los requisitos de la BD de viajes (de la segunda clase de este tema), pero el SGBD a usar será mongoDB. La BD se llamará viajes.

  1. ¿Hay algún cambio en el modelo conceptual?

  2. ¿Y en el lógico? ¿En el físico?

  3. Implementa tu modelo de datos (documental)

No hace falta insertar datos, lo haremos en la clase siguiente.

8.5. Añadir documentos

Video Clase

  1. Para insertar documentos se usan dos métodos:

    1. db.collection.insertOne(el_objeto), que inserta «el_objeto» en la colección «collection» de la BD

    2. db.collection.insertMany(varios_objeto), que inserta «varios_objetos» (array de objetos: separados por comas) en la colección «collection» de la BD

    db.inventory.insertOne(
        { item: "canvas", qty: 100, tags: ["cotton"], size: { h: 28, w: 35.5, uom: "cm" } }
    )
    
    db.inventory.insertMany([
        { item: "journal", qty: 25, tags: ["blank", "red"], size: { h: 14, w: 21, uom: "cm" } },
        { item: "mat", qty: 85, tags: ["gray"], size: { h: 27.9, w: 35.5, uom: "cm" } },
        { item: "mousepad", qty: 25, tags: ["gel", "blue"], size: { h: 19, w: 22.85, uom: "cm" } }
    ])
    
  2. Al insertar:

    1. Si todavía no existe la colección (o la BD) la crea

    2. Se añade el campo especial _id al objeto (identificador único del documento)

    3. Te devuelve un documento con el resultado de la inserción

  3. Para ver los documentos de nuestra colección usamos db.collection.find( {} )

    1. Usa el método find para buscar documentos en la colección collection

    2. Usa un «documento especial de condición» (vacío, es decir, todos en este caso)

    3. Devuelve todos los documentos (porque cumplen la condición)

    4. El «documento de condición» puede ser mucho más complejo (lo vemos en la siguiente clase)

Añadir documentos en MongoDB

Diseña una base de datos con documentos que representen frutas con las siguientes características:

  1. tipo de fruta

  2. país de origen

  3. diámetro de la pieza

Los documentos se guardarán en la colección Frutas dentro de la base de datos Tienda.

Añadir documentos a BD Viajes

Añade tres registros en cada una de las colecciones que hayas creado para tu BD de Viajes

8.6. Consultar Documentos

Video Clase

  1. Hay un tipo de documento especial (query filter document ) que se usa para especificar varias condiciones (en varias acciones):

    1. Usa condiciones de igualdad: campo: valor

    2. O también operadores (operator) para especificar la condición

    3. Se pueden incluir varias condiciones

      {
        <field1>: <value1>,
        <field2>: { <operator>: <value> },
        ...
      }
      
    4. El filtro más trivial es el vacío {}, que muestra todos los documentos de la colección

  2. Usamos db.collection.find(query filter document) . Busca en la colección «collection» los documentos que cumplan con la condición

  3. Algunos ejemplos:

    1. Condiciones de igualdad:

      db.inventory.find( { status: "D" } )
      
      db.posts.find( {category: "News"} )
      
    2. Usando un operador:

      db.inventory.find( { status: { $in: [ "A", "D" ] } } )
      db.inventory.find( { quantity: { $gt: 20 } } )
      
    3. Usando AND (añadir pares campo/valor)

      db.inventory.find( { status: "A", qty: { $lt: 30 } } )
      
    4. Usando OR (implica usar el operador or y una lista de operandos)

      // Permite mezclar AND (las comas)
      // y OR (a través del array de opciones y el operador $or
      
      db.inventory.find( {
             status: "A",
             $or: [ { qty: { $lt: 30 } }, { item: /^p/ } ]
      } )
      
  4. Hay muchos tipos de operadores para crear las expresiones de filtros:

    1. De comparación

    2. Los lógicos

    3. Sobre elementos

    4. … y muchos más

Ejercicios de consulta de datos en Mongodb

Haz los ejercicios de consulta en Ejercicios con MongoDB (en Cloud y mongosh)

8.7. Actualizar y Borrar Documentos

Video Clase

  1. Para actualizar documentos hay que especificar:

    1. La condición (query filter document)

    2. Las instrucciones de actualización

    3. Y se incluyen en el método que se usa:

      db.collection.updateOne(<filter>, <update>, <options>)
      db.collection.updateMany(<filter>, <update>, <options>)
      
    4. También existe un campos de opciones (si quisieras personalizar la configuración)

  2. Hay operadores específicos para actualizar elementos

    1. Procesas los campos a actualizar, según el operador

    2. Hay muchos (update operators ), algunos ejemplos sencillos:

      1. $set: para actualizar un campo a un valor

      2. $currentDate: para actualizar un campo al timestamp actual

      3. $inc: para incrementar el valor del campo

  3. Algunos ejemplos de actualización:

    // Actualiza el primer documento que encuentre que
    // tenga el valor "paper" en el campo "item"
    //Y actualiza los campos size.uom (uom del documento size), cm y lastModified
    
    db.inventory.updateOne(
           { item: "paper" },
           {
             $set: { "size.uom": "cm", status: "P" },
             $currentDate: { lastModified: true }
           }
    )
    
    // para actualizar todos los documentos que cumplan la condición
    // simplemente habría que usar db.inventory.updateMany
    
    // Se podría usar  db.collection.replaceOne(<filter>, <update>, <options>)
    // para sustituir todo el documento (no sólo actualizar campos)
    
  4. Para borrar documentos , se pueden usar los siguientes métodos:

    1. db.collection.deleteOne(filtro): borrará el primer documento que cumpla con la condición (filtro de consulta)

    2. db.collection.deleteMany(filtro), que borra todos los elementos de «collection» que cumplan con la condición

    // Borra todos los documentos en una colección
    db.inventory.deleteMany({})
    
    // Borra todos los documentos con el campo status igual a A
    db.inventory.deleteMany({ status : "A" })
    
    // Borra el primero que encuentra (status igual a D)
    db.inventory.deleteOne( { status: "D" } )
    

Actualización y borrado de Documentos

Haz los ejercicios de actualización y borrado en Ejercicios con MongoDB (en Cloud y mongosh)

8.8. Proyección y Agregación

Video Clase

  1. Uso del mecanismo de Proyección

    1. Cuando quiero consultar, a veces necesito seleccionar qué campos mostrar

    2. Aunque hay operadores especializados, lo más fácil es especificar qué campos mostrar (o los que se excluyen)

    3. Usas el valor 1 para mostrar, y el 0 para excluir

    4. Por ejemplo:

      // Devuelve documento con title y date)
      db.posts.find({}, {title: 1, date: 1})
      
      // Devuelve documentos con todos los campos, excluyendo status e instock, con status="A"
      db.inventory.find( { status: "A" }, { status: 0, instock: 0 } )
      // Devuelve documentos con status e instock, de los que status="A"
      db.inventory.find( { status: "A" }, { item: 1, status: 1 } )
      
      // Y se pueden incluir campos de un objeto (anidado)
      // uom es un campo de size, que a su vez es campo del documento principal
      db.inventory.find(
        { status: "A" },
        { item: 1, status: 1, "size.uom": 1 }
      )
      
  2. Agregación

    1. Permiten generar un conjunto de pasos (etapas = stages) a realizar sobre los datos (agregation stages)

    2. La salida es un documento que sirve de entrada a la siguiente etapa (concepto de pipeline -> tubería)

    3. Usando un tipo de operadores especiales (hay muchos aggregation operators )

    4. Se usa el método db.collection.aggregate

    5. Se puede personalizar mucho, pero algunos ejemplos sencillos:

      1. $group: permite agrupar por un campo concreto (mongodb_aggregations_group)

        // OJO que este _id no es el _id del documento (clave primaria)
        // Simplemente será la clave primaria del NUEVO documento
        
        db.listingsAndReviews.aggregate(
            [ { $group : { _id : "$property_type" } } ]
        )
        
      2. $limit: para seleccionar sólo un número de documentos (mongodb_aggregations_limit)

        db.movies.aggregate([ { $limit: 1 } ])
        
      3. $project: para usar sólo algunos campos (mecanismo de proyección mongodb_aggregations_project)

        // Stage 1: selecciona los campos name, cuisine y address
        // Stage 2: sólo 5 documentos
        db.restaurants.aggregate([
         {
           $project: {
           "name": 1,
           "cuisine": 1,
           "address": 1
           }
         },
         {
          $limit: 5
         }
         ])
        
      4. $sort: para ordenar según uno (o más) campos (mongodb_aggregations_sort)

         // Lo ordena por el campo acommodates (en orden descendente)
         db.listingsAndReviews.aggregate([
         {
           $sort: { "accommodates": -1 }
         },
         {
           $project: {
              "name": 1,
              "accommodates": 1
           }
        },
        {
           $limit: 5
        }
        ])
        
      5. $match: para filtrar según el filtro de consulta, similar a find (mongodb_aggregations_match )

        //Filtra los que el campo property_type sea "House"
        
        db.listingsAndReviews.aggregate([
               { $match : { property_type : "House" } },
               { $limit: 2 },
               { $project: {
                   "name": 1,
                   "bedrooms": 1,
                   "price": 1
               }}
        ])
        
      6. $addFields: para añadir campos al documento resultado, sobre el que puedes usar operadores de agregación (mongodb_aggregations_addFields). En las versiones nuevas ya se usa $set (más útil y simplificado)

        db.restaurants.aggregate([
        {
               $addFields: {
               avgGrade: { $avg: "$grades.score" }
               }
        },
        {
               $project: {
                   "name": 1,
                   "avgGrade": 1
               }
        },
        {
               $limit: 5
        }
        ])
        
      7. $count: para contar el número de documentos que llegan a esa etapa (generalmente la última) (mongodb_aggregations_count)

        db.restaurants.aggregate([
        {
               $match: { "cuisine": "Chinese" }
        },
        {
               $count: "totalChinese"
        }
        ])
        
      8. $out: para generar una colección, como resultado de todas las etapas anteriores (mongodb_aggregations_out)

        // Crea la colección "properties_by_type"
        // agrupando por "property_type" y
        // en el campo properties un array con tres elementos
        // $push es una operador de agregación que devuelve un array con esos elementos (los cuenta para cada property_type
        
        db.listingsAndReviews.aggregate([
        {
               $group: {
                    _id: "$property_type",
                    properties: {
                         $push: {
                             name: "$name",
                             accommodates: "$accommodates",
                             price: "$price",
                         },
                    },
               },
        },
        { $out: "properties_by_type" },
        ])
        
    6. Se podría usar una especie de inner join con el operador $lookup (mongodb_aggregations_lookup):

      1. Siempre existirá el campo especial _id como clave primaria

      2. Se usa un campo que sea clave primaria en una de las colecciones

        // Hace una "búsqueda" de movie_id de
        // la colección movies en la
        // colección movie_details (en el _id que sería su clave primaria)
        // movie_details debería tener el campo movie_id como _id
        
        db.comments.aggregate([
        {
            $lookup: {
                  from: "movies",
                  localField: "movie_id",
                  foreignField: "_id",
                  as: "movie_details",
        },
        },
        {
            $limit: 1
        }
        ])
        

Uso de proyección y agregación

Haz los ejercicios de proyección y agregacion en Ejercicios con MongoDB (en Cloud y mongosh)