8. Bases de Datos NoSQL¶
En este tema aprenderemos a:
Conocer qué son las bases de datos NoSQL
Usar Postgres como almacén de documentos JSON
Usar MongoDB como un ejemplo de BD NoSQL
Esfuerzo Necesario
El curso está organizado en 8 sesiones de clase. Cada clase (sesión) implica una dedicación de entre 2 y 4 horas.
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
-
Not only SQL: no usan SQL (en general)
No usan el modelo relacional
Sistemas muy heterogéneos:
Tanto el funcionamiento del Sistema gestor
como el modelo de datos (almacenamiento estructurado)
Rendimiento y escalabilidad muy altas pero …
… a coste de funcionalidad (JOIN, soporte transaccional, integridad de datos, tipo de almacenamiento, etc)
Generalmente con arquitecturas distribuidas (descentralizadas y redundantes)
Muy usadas en desarrollo web porque:
Importa más la velocidad que la integridad (se hace a nivel aplicación)
Porque se necesita rendimiento y escalabilidad (salvajes)
Se puede «delegar» la complejidad de un SGBR (microservicios con APIs) y aumentar la redundancia
Se busca descentralizar la aplicación para mejorar:
concurrencia
escalabilidad
disponibilidad
Almacenamiento estruturado (no relacional y con integridad sencilla). Muchos tipos:
Usando pares clave/valor. Por ejemplo arrays asociativos o pares de clave = a valor (objeto sin estructura)
Usando objetos (JSON por ejemplo) y/o colecciones de objetos
Orientadas a grafos: nodos de almacenamiento relacionados entre sí (similar a un modelo en red)
Uso de documentos XML
Sistemas Gestores de BD NoSQL (hay muchos). Estos son 5 ejemplos:
Apache Casandra . Almacenamiento tablas particionadas y distribuidas.
Redis . Almacenamiento hash (clave=valor), muy usado como almacenamiento temporal (RAM)
MongoDB. Almacenamiento Documental (Objetos) y muy popular
Apache CouchDB . Almacenamiento Documental (Objetos JSON), accesible vía API y replicable
Google BigTable . Almacenamiento en tablas multidimensionales (sistema GFS), distribuido y muy eficiente
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)
¿Cómo se instalaría? ¿Cual es su documentación oficial?
¿Cómo me conecto? ¿Tiene soporte para conectarme desde la red?
¿Cómo sería un ejemplo de almacenamiento? ¿Cual podría ser un modelo de datos? ¿Qué es lo que puedo almacenar?
¿Cómo consultaría los datos? ¿En qué lenguaje?
¿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
Usar Postgres como SGBD Objeto Relacional:
Puede almacenar documentos XML y objetos JSON
Ofrece operadores y funciones para gestionarlos
Usando la «lógica» SQL (lenguaje declarativo)
Tipo de datos JSON (datatype-json):
Basado en el estándar JSON (RFC 7159)
Valores (string / number /boolean), arrays y objetos anidados
Va entre comillas (un JSON se puede representar como una cadena de texto con una estructura).
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
Usando JSON:
Se usan como un tipo de datos sql más (como una cadena de texto «especial») pero …
… puedes usar los operadores especiales para JSON
Hay muchas opciones (functions-json)
Los más sencillos sería usar
-> si usamos el índice (número)
->> 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'
Y también hay funciones especiales (functions-json)
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"}
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:
Crea la tabla ejemplo con dos campos, uno la clave primaria y otro de tipo jsonb
Añade 5 objetos JSON que representen a una persona. La estructura no tiene porqué ser la misma
Consulta la tabla, por ejemplo:
El nombre de una persona concreta, conociendo su dni
Mostrar el nombre y apellido de todas las personas que tengas almacenadas
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:
Necesitamos conocer la información de los viajeros y los vuelos que hacen.
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
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:
Creas los objetos de la base de datos
Le añades al menos 3 registros de ejemplo de contenido (para no tener sólo la estructura, sino también el contenido)
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
MongoDB es un SGBD:
Orientado a Documentos: almacena colecciones de documentos en formato (casi) JSON
Muy flexible, replicable, redundante y rápida
De código abierto (aunque una licencia especial )
Muy usada en desarrollos web por su flexibilidad
Con limitaciones importantes si el modelo de datos o las necesidades de integridad son muy importantes
Se puede instalar en múltiples plataformas :
En tu propio equipo
En su equipo (Cloud). Por ejemplo MongoDB Atlas
Lo que vamos a usar:
Servidor Cloud (no en local). Te das de alta (y creas tu sistema)
Cliente vía consola (mongosh). Lo instalas en tu equipo.
Algunos conceptos (mongodb_get_started)
Almacenamiento en tablas (Relacional) vs objetos (por ejemplo documentos)
Servidor en local o en la nube (Cloud)
Cluster: tu servidor de BD (el SGBD y el almacenamiento para las BD)
La conexión:
La cadena de conexión: parámetros necesarios para conectarse
El software de conexión (múltiples):
Hay muchos: cliente CLI (mongosh), cliente GUI (Compass), software (drivers según lenguaje), API (aplicación web), etc
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
Creando una BD (mongodb_mongosh_create_database)
Creas una BD
Te conectas
show databases; use mi_bd_ejemplo db
Añades información
Añades documentos (en realidad creas colecciones, que tienen documentos) usándo métodos (recuerda la POO):
Y puedes ir añadiendo objetos (documentos) a esa colección
db.createCollection("tu_coleccion") db.tu_coleccion.insertOne(object)
Y puedes borrarlas:
La colección: db.collection.drop
Y la base de datos: db.dropDatabase
Los documentos BSON (objetos):
Como JSON pero con representación binaria (BSON)
Estructura en base a pares clave: valor
El valor del campo puede ser muchas cosas (es muy flexible en esto)
Las claves son siempre cadenas de texto (string)
Se pueden incluir documentos como valores (embedded documents)
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" } } }
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
Crea una BD que se llame ejemplo
Añade tres colecciones: una, dos y tres
Revisa qué BD hay en el «cluster»
Revisa qué colecciones hay en la BD «ejemplo»
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.
¿Hay algún cambio en el modelo conceptual?
¿Y en el lógico? ¿En el físico?
Implementa tu modelo de datos (documental)
No hace falta insertar datos, lo haremos en la clase siguiente.
8.5. Añadir documentos¶
Video Clase
Para insertar documentos se usan dos métodos:
db.collection.insertOne(el_objeto), que inserta «el_objeto» en la colección «collection» de la BD
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" } } ])
Al insertar:
Si todavía no existe la colección (o la BD) la crea
Se añade el campo especial _id al objeto (identificador único del documento)
Te devuelve un documento con el resultado de la inserción
Para ver los documentos de nuestra colección usamos db.collection.find( {} )
Usa el método find para buscar documentos en la colección collection
Usa un «documento especial de condición» (vacío, es decir, todos en este caso)
Devuelve todos los documentos (porque cumplen la condición)
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:
tipo de fruta
país de origen
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
Hay un tipo de documento especial (query filter document ) que se usa para especificar varias condiciones (en varias acciones):
Usa condiciones de igualdad: campo: valor
O también operadores (operator) para especificar la condición
Se pueden incluir varias condiciones
{ <field1>: <value1>, <field2>: { <operator>: <value> }, ... }
El filtro más trivial es el vacío
{}
, que muestra todos los documentos de la colección
Usamos db.collection.find(query filter document) . Busca en la colección «collection» los documentos que cumplan con la condición
Algunos ejemplos:
Condiciones de igualdad:
db.inventory.find( { status: "D" } ) db.posts.find( {category: "News"} )
Usando un operador:
db.inventory.find( { status: { $in: [ "A", "D" ] } } ) db.inventory.find( { quantity: { $gt: 20 } } )
Usando AND (añadir pares campo/valor)
db.inventory.find( { status: "A", qty: { $lt: 30 } } )
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/ } ] } )
Hay muchos tipos de operadores para crear las expresiones de filtros:
De comparación
Los lógicos
Sobre elementos
… 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
Para actualizar documentos hay que especificar:
La condición (query filter document)
Las instrucciones de actualización
Y se incluyen en el método que se usa:
db.collection.updateOne(<filter>, <update>, <options>) db.collection.updateMany(<filter>, <update>, <options>)
También existe un campos de opciones (si quisieras personalizar la configuración)
Hay operadores específicos para actualizar elementos
Procesas los campos a actualizar, según el operador
Hay muchos (update operators ), algunos ejemplos sencillos:
$set: para actualizar un campo a un valor
$currentDate: para actualizar un campo al timestamp actual
$inc: para incrementar el valor del campo
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)
Para borrar documentos , se pueden usar los siguientes métodos:
db.collection.deleteOne(filtro): borrará el primer documento que cumpla con la condición (filtro de consulta)
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
Uso del mecanismo de Proyección
Cuando quiero consultar, a veces necesito seleccionar qué campos mostrar
Aunque hay operadores especializados, lo más fácil es especificar qué campos mostrar (o los que se excluyen)
Usas el valor 1 para mostrar, y el 0 para excluir
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 } )
Agregación
Permiten generar un conjunto de pasos (etapas = stages) a realizar sobre los datos (agregation stages)
La salida es un documento que sirve de entrada a la siguiente etapa (concepto de pipeline -> tubería)
Usando un tipo de operadores especiales (hay muchos aggregation operators )
Se usa el método db.collection.aggregate
Se puede personalizar mucho, pero algunos ejemplos sencillos:
$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" } } ] )
$limit: para seleccionar sólo un número de documentos (mongodb_aggregations_limit)
db.movies.aggregate([ { $limit: 1 } ])
$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 } ])
$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 } ])
$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 }} ])
$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 } ])
$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" } ])
$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" }, ])
Se podría usar una especie de inner join con el operador $lookup (mongodb_aggregations_lookup):
Siempre existirá el campo especial _id como clave primaria
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)