Santiago Quiñones Cuenca
Software Developer and Educator, Master in Software Engineering, Research UTPL {Loja, Ecuador} Repositories: http://github.com/lsantiago
Programación funcional y reactiva - Computación
Trabajando con bases de datos relacionales
Trabajo con una base de datos
Fundamentos
Existen diferentes técnicas para trabajar con bases de datos
Pero, todas parten de un principio:
Conexión a la base
Enviar sentencias SQL que se ejecutarán
Recuperar los resultados y trabajar
Trabajo con una base de datos
Fundamentos
En programación, lo único que cambia es quien escribe el SQL
Manual
Semi-automático
Automático
Mapeo
Objeto Relación
Trabajo con una base de datos
Desde el nivel más bajo
Trabajo con bases de datos relacionales
Arquitectura
BASE DE DATOS
Algunos comandos útiles
mysql -u root -p
show databases;
CREATE DATABASE my_db;
use my_db;
show tables;BASE DE DATOS
Script
CREATE TABLE MOVIE (
ID int(11) NOT NULL AUTO_INCREMENT,
ORIGINAL_TITLE varchar(1024) DEFAULT NULL,
ID_RAW_DATA int(11) DEFAULT NULL,
BUDGET double DEFAULT NULL,
REVENUE bigint(14) DEFAULT NULL,
RUNTIME int(11) DEFAULT NULL,
RELEASE_DATE date DEFAULT NULL,
PRIMARY KEY (ID)
);BASE DE DATOS
Script
INSERT INTO MOVIE (ORIGINAL_TITLE, ID_RAW_DATA, BUDGET, REVENUE, RUNTIME, RELEASE_DATE)
VALUES
('Avatar', 19995, 237000000, 2787965087, 162, '2009-12-10'),
('Pirates of the Caribbean: At World''s End', 285, 300000000, 961000000, 169, '2007-05-19'),
('Spectre', 206647, 245000000, 880674609, 148, '2015-10-26'),
('The Dark Knight Rises', 49026, 250000000, 1084939099, 165, '2012-07-16'),
('John Carter', 49529, 260000000, 284139100, 132, '2012-03-07'),
('Spider-Man 3', 559, 258000000, 890871626, 139, '2007-05-01'),
('Tangled', 38757, 260000000, 591794936, 100, '2010-11-24'),
('Avengers: Age of Ultron', 99861, 280000000, 1405403694, 141, '2015-04-22'),
('Harry Potter and the Half-Blood Prince', 767, 250000000, 933959197, 153, '2009-07-07'),
('Batman v Superman: Dawn of Justice', 209112, 250000000, 873260194, 151, '2016-03-23');Scala y Base de datos relacional
doobie library
Scala y Base de datos relacional
doobie library
Scala y Base de datos relacional
doobie library - dependencias
"org.tpolecat" %% "doobie-core" % "1.0.0-RC11", // Dependencias de doobie
"org.tpolecat" %% "doobie-hikari" % "1.0.0-RC11", // Para gestión de conexiones
"com.mysql" % "mysql-connector-j" % "9.1.0", // Driver para MySQL
"com.typesafe" % "config" % "1.4.2", // Para gestión de archivos de configuración
"org.slf4j" % "slf4j-simple" % "2.0.16" // Implementaciónd de loggin
Scala y Base de datos relacional
doobie library - todo el código
import doobie._
import doobie.implicits._
import cats.effect._
/**
* Objeto principal que extiende de IOApp.Simple.
* IOApp gestiona automáticamente el ciclo de vida de la aplicación y el Runtime de Cats Effect.
*/
object EjemploBase extends IOApp.Simple {
// Modelo de datos que mapea una fila de la tabla MOVIE a un objeto de Scala
case class Movie(id: Int, originalTitle: String)
}- base para trabajar con BD (Transactor, Query)
- facilita las consultas, y permite transformar resultados de consultas
- ayuda en el manejo de cálculos que pueden fallar, en la transformación de datos
- manejo de efecto secundarios de manera funcional (IO: modela E/S, Resource: manejo de recursos que necesitan ser abiertos - BD)
Scala y Base de datos relacional
doobie library - conexión
import doobie._
import doobie.implicits._
import cats.effect._
/**
* Objeto principal que extiende de IOApp.Simple.
* IOApp gestiona automáticamente el ciclo de vida de la aplicación y el Runtime de Cats Effect.
*/
object EjemploBase extends IOApp.Simple {
// Modelo de datos que mapea una fila de la tabla MOVIE a un objeto de Scala
case class Movie(id: Int, originalTitle: String)
/**
* El Transactor es el puente entre Scala y la base de Datos.
* Se encarga de:
* 1. Cargar el driver JDBC.
* 2. Gestionar la apertura y cierre de conexiones.
* 3. Envolver las operaciones en transacciones SQL.
*/
private val xa = Transactor.fromDriverManager[IO](
driver = "com.mysql.cj.jdbc.Driver",
url = "jdbc:mysql://localhost:3306/my_db",
user = "root",
password = "integrador",
logHandler = None
)
}- El Transactor se encarga de establecer y manejar la conexión con la base de datos
Scala y Base de datos relacional
doobie library - búsqueda
import doobie._
import doobie.implicits._
import cats.effect._
/**
* Objeto principal que extiende de IOApp.Simple.
* IOApp gestiona automáticamente el ciclo de vida de la aplicación y el Runtime de Cats Effect.
*/
object EjemploBase extends IOApp.Simple {
// Modelo de datos que mapea una fila de la tabla MOVIE a un objeto de Scala
case class Movie(id: Int, originalTitle: String)
/**
* El Transactor es el puente entre Scala y la base de Datos.
* Se encarga de:
* 1. Cargar el driver JDBC.
* 2. Gestionar la apertura y cierre de conexiones.
* 3. Envolver las operaciones en transacciones SQL.
*/
private val xa = Transactor.fromDriverManager[IO](
driver = "com.mysql.cj.jdbc.Driver",
url = "jdbc:mysql://localhost:3306/my_db",
user = "root",
password = "integrador",
logHandler = None
)
/**
* Busca una película por su ID.
* Devuelve un ConnectionIO[Option], lo que significa que es una descripción de una consulta
* que puede devolver una película o nada (None) si el ID no existe.
*/
private def find(id: Int): ConnectionIO[Option[Movie]] =
sql"SELECT ID, ORIGINAL_TITLE FROM MOVIE WHERE ID = $id"
.query[Movie] // Mapea el resultado a la case class Movie
.option // Devuelve Option[Movie]
/**
* Método run: Punto de entrada de la aplicación funcional.
* Aquí se "interpretan" las descripciones ConnectionIO convirtiéndolas en efectos IO
* mediante el método .transact(xa).
*/
override def run: IO[Unit] = {
for {
// Ejecuta la búsqueda y maneja el caso de que no exista el registro
res <- find(4).transact(xa)
_ <- IO.println(s"Película encontrada: ${res.map(_.originalTitle).getOrElse("No existe en la DB")}")
} yield () // El for-comprehension termina devolviendo una unidad pura dentro de IO
}
}Scala y Base de datos relacional
doobie library - búsqueda e inserción
import doobie._
import doobie.implicits._
import cats.effect._
/**
* Objeto principal que extiende de IOApp.Simple.
* IOApp gestiona automáticamente el ciclo de vida de la aplicación y el Runtime de Cats Effect.
*/
object EjemploBase extends IOApp.Simple {
// Modelo de datos que mapea una fila de la tabla MOVIE a un objeto de Scala
case class Movie(id: Int, originalTitle: String)
/**
* El Transactor es el puente entre Scala y la base de Datos.
* Se encarga de:
* 1. Cargar el driver JDBC.
* 2. Gestionar la apertura y cierre de conexiones.
* 3. Envolver las operaciones en transacciones SQL.
*/
private val xa = Transactor.fromDriverManager[IO](
driver = "com.mysql.cj.jdbc.Driver",
url = "jdbc:mysql://localhost:3306/my_db",
user = "root",
password = "integrador",
logHandler = None
)
/**
* Busca una película por su ID.
* Devuelve un ConnectionIO[Option], lo que significa que es una descripción de una consulta
* que puede devolver una película o nada (None) si el ID no existe.
*/
private def find(id: Int): ConnectionIO[Option[Movie]] =
sql"SELECT ID, ORIGINAL_TITLE FROM MOVIE WHERE ID = $id"
.query[Movie] // Mapea el resultado a la case class Movie
.option // Devuelve Option[Movie]
/**
* Recupera todos los registros de la tabla MOVIE.
* .to[List] acumula todas las filas resultantes en una lista de Scala.
*/
private def listAllMovies(): ConnectionIO[List[Movie]] =
sql"SELECT ID, ORIGINAL_TITLE FROM MOVIE"
.query[Movie]
.to[List]
/**
* Inserta un nuevo registro en la tabla MOVIE.
* .update indica que es una operación de escritura (DML).
* .run devuelve el número de filas afectadas (Int).
*/
private def insertMovie(originalTitle: String, idRawData: String, budget: String, revenue: String, runtime: String, releaseDate: String): ConnectionIO[Int] =
sql"""
INSERT INTO MOVIE (ORIGINAL_TITLE, ID_RAW_DATA, BUDGET, REVENUE, RUNTIME, RELEASE_DATE)
VALUES ($originalTitle, $idRawData, $budget, $revenue, $runtime, $releaseDate)
""".update.run
/**
* Método run: Punto de entrada de la aplicación funcional.
* Aquí se "interpretan" las descripciones ConnectionIO convirtiéndolas en efectos IO
* mediante el método .transact(xa).
*/
override def run: IO[Unit] = {
// Datos de ejemplo para la inserción
val newRow = ("Adventure Fantasy Action Science Fiction", "1452", "27000000", "39108192", "154", "2006-06-28")
for {
// Ejecuta la búsqueda y maneja el caso de que no exista el registro
res <- find(4).transact(xa)
_ <- IO.println(s"Película encontrada: ${res.map(_.originalTitle).getOrElse("No existe en la DB")}")
// Ejecuta la inserción y recupera cuántas filas se insertaron
rows <- insertMovie(newRow._1, newRow._2, newRow._3, newRow._4, newRow._5, newRow._6).transact(xa)
_ <- IO.println(s"✅ Filas insertadas: $rows")
// Recupera y recorre la lista de películas
movies <- listAllMovies().transact(xa)
_ <- IO.println("--- Listado Completo ---")
_ <- IO(movies.foreach(m => println(s"ID: ${m.id} | Título: ${m.originalTitle}")))
} yield () // El for-comprehension termina devolviendo una unidad pura dentro de IO
}
}Reto: Poblar BD de temperturas
Reto
Archivo CSV (temperaturas.csv)
dia,temp_dia,temp_tarde
1,18.5,24.3
2,17.8,25.1
3,19.2,23.8
4,16.5,26.2
5,18.9,24.5
6,17.2,25.8Reto
Organización de solución
project-root/
├── build.sbt
├── src/
│ ├── main/
│ │ ├── scala/
│ │ │ ├── config/
│ │ │ │ └── Database.scala
│ │ │ ├── models/
│ │ │ │ └── Temperatura.scala
│ │ │ ├── dao/
│ │ │ │ └── TemperaturaDAO.scala
│ │ │ ├── services/
│ │ │ │ ├── Estadistica.scala
│ │ │ │ └── DataCleaner.scala
│ │ │ └── Main.scala
│ │ └── resources/
│ │ ├── application.conf
│ │ ├── logback.xml
│ │ └── data/
│ │ └── temperaturas.csvReto
Organización de solución
Temperaturas2026/
├── src/
│ ├── main/
│ │ ├── resources/
│ │ │ ├── data/
│ │ │ │ └── temperaturas.csv # Archivo fuente con los registros climáticos
│ │ │ └── application.conf # Configuración de acceso a MySQL/MariaDB
│ │ └── scala/
│ │ ├── config/
│ │ │ └── Database.scala # Configuración del Transactor para Doobie
│ │ ├── dao/
│ │ │ └── TemperaturaDAO.scala # Consultas SQL y persistencia
│ │ ├── models/
│ │ │ └── Temperatura.scala # Case Class del modelo de datos
│ │ ├── services/
│ │ │ ├── TemperaturaService.scala # Orquestador de lógica de negocio
│ │ │ ├── Limpieza.scala # Filtrado y depuración de datos
│ │ │ └── Estadisticas.scala # Procesamiento analítico y cálculos
│ │ ├── ListBasedLoaderMain.scala # Carga de datos mediante colecciones
│ │ └── StreamingInsertMain.scala # Inserción masiva usando FS2 Streaming
└── build.sbt # Gestión de dependencias (Doobie, FS2, Circe)Reto
Creación de tabla
CREATE TABLE temperaturas (
dia INT PRIMARY KEY,
temperatura_mañana FLOAT,
temperatura_tarde FLOAT
);
Reto
Proyecto Integrador
Proyecto Integrador o bimestral
Detalles
La presentación del proyecto debe incluir:
Código cargado en GitHub (se revisará el trabajo progresivo)
Proyecto Integrador o bimestral
Detalles
La presentación del proyecto debe incluir
Formato Wiki (archivo README.md):
• Entrega del 30 al 31 de enero de 2025
Proyecto Integrador o bimestral
Recordando criterios de limpieza
# Limpieza de Datos
## Manejo de Datos Faltantes
- Examinar valores nulos en columnas numéricas (budget, revenue, runtime)
- Detectar registros sin release_date
- Evaluar campos JSON incompletos (production_companies, production_countries, spoken_languages)
## Formato de Fechas
- Convertir release_date a YYYY-MM-DD
- Identificar y corregir fechas futuras
## Validación Numérica
- Asegurar valores positivos en budget y revenue
- Verificar rango razonable de runtime
- Estandarizar decimales en vote_average
## Limpieza JSON
- Normalizar formato en campos JSON (production_companies, production_countries, spoken_languages, crew)
- Reemplazar comillas simples por dobles
- Validar estructura de arrays
- Manejar caracteres especiales
## Limpieza de Texto
- Eliminar espacios múltiples en original_title y overview
- Asegurar codificación UTF-8
- Eliminar líneas vacías y saltos innecesarios
## Validaciones de Campos
- vote_count: enteros positivos
- vote_average: rango 0-10
- popularity: valores positivos
## Control de Calidad
- Identificar registros duplicados por ID
- Verificar codificación UTF-8 del archivoProyecto Integrador o bimestral
Detalles
Se calificara los siguientes ítems:
By Santiago Quiñones Cuenca
Persistencia de datos en base de datos relacionales
Software Developer and Educator, Master in Software Engineering, Research UTPL {Loja, Ecuador} Repositories: http://github.com/lsantiago