Capitulo 01
Fundamentos de Bases de Datos
Del modelo relacional a un pipeline que entrega datos confiables a Machine Learning
Arquitectura relacional, DML, DCL, modelado ER, normalizacion, SQLite, herramientas distribuidas y preparacion de datos para ML.
Un modelo aprende de una representacion de datos. Si las tablas tienen redundancias, tipos incorrectos, permisos excesivos o relaciones ambiguas, el problema aparece como leakage, features duplicadas o resultados imposibles de auditar.
La calidad del modelo no puede superar la calidad de la representacion que recibe.
— Principio de arquitectura de datos
1.1 Arquitectura de una base de datos relacional
Una base relacional organiza entidades en tablas y conecta esas tablas mediante claves.
clientes ventas productos
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ id_cliente PK │◄───────────────┤ id_cliente FK│ │ id_producto PK│
│ nombre │ │ id_producto FK├──────────────►│ nombre │
│ segmento │ │ cantidad │ │ categoria │
└──────────────┘ │ fecha │ │ precio │
└──────────────┘ └──────────────┘
Componentes
| Componente | Funcion |
|---|---|
| Tabla | Agrupa registros de una entidad. |
| Fila | Representa una instancia concreta. |
| Columna | Describe un atributo y su tipo. |
| Clave primaria | Identifica una fila sin ambiguedad. |
| Clave foranea | Conecta una fila con otra tabla. |
| Restriccion | Protege reglas como unicidad, no nulos y rangos validos. |
Esquema relacional
M1Contrato estructural de una base: tablas, columnas, tipos de datos, claves, relaciones y restricciones.
Ej: ventas(id_venta PK, id_cliente FK, fecha, cantidad).
Que problema resuelve una base de datos
Una base de datos no es solamente un archivo grande. Es un sistema que debe conservar reglas mientras muchas personas o procesos leen y escriben al mismo tiempo.
| Necesidad | Mecanismo |
|---|---|
| Evitar dos IDs iguales | Clave primaria y restriccion UNIQUE. |
| Evitar ventas sin cliente valido | Clave foranea. |
| Evitar cantidades imposibles | CHECK (cantidad > 0). |
| Mantener una operacion completa | Transacciones. |
| Consultar sin conocer el almacenamiento fisico | SQL y el optimizador. |
ACID en lenguaje simple
Las bases transaccionales suelen describirse con ACID:
- Atomicidad: la operacion ocurre completa o no ocurre.
- Consistencia: las restricciones siguen siendo verdaderas antes y despues.
- Aislamiento: una transaccion intermedia no se confunde con otra.
- Durabilidad: despues de
COMMIT, el cambio persiste aunque el proceso termine.
No todos los motores priorizan estas propiedades de la misma manera. Elegir una base es elegir tambien que garantias necesita el negocio.
Fundamentos de bases de datos
Una base de datos es un sistema para almacenar, organizar, consultar y proteger informacion manteniendo reglas verificables. No es solamente un archivo con filas: tambien incluye un esquema, restricciones, un motor de ejecucion y mecanismos para controlar cambios.
Conviene distinguir cuatro elementos:
| Elemento | Pregunta que responde |
|---|---|
| Esquema | ¿Que tablas, columnas, tipos y relaciones existen? |
| Datos | ¿Que hechos concretos estan registrados ahora? |
| Restricciones | ¿Que valores o relaciones no estan permitidos? |
| Transacciones | ¿Como se agrupan cambios para que sean seguros? |
En analisis de datos, esta distincion importa porque una consulta puede ser sintacticamente correcta y aun asi producir resultados incorrectos si el esquema permite duplicados, relaciones rotas o valores fuera de rango. Antes de usar una tabla como fuente de features, revisa su granularidad, sus claves, sus nulos y la fecha de actualizacion.
Una tabla no es confiable solo porque tiene datos. Es confiable cuando sus columnas tienen un significado claro, sus relaciones pueden comprobarse y sus restricciones evitan estados imposibles.
1.2 DML: consultar y modificar datos
DML significa Data Manipulation Language. Incluye las operaciones sobre los registros.
-- SELECT: consultar
SELECT producto, precio_unitario
FROM ventas_tecnologia
WHERE pais = 'Colombia';
-- INSERT: agregar
INSERT INTO ventas_tecnologia
(id_venta, producto, categoria, precio_unitario, cantidad, fecha, pais)
VALUES
(28, 'Teclado mecanico', 'Teclados', 145.00, 2, '2026-02-11', 'Colombia');
-- UPDATE: modificar con filtro seguro
UPDATE ventas_tecnologia
SET categoria = 'Perifericos'
WHERE producto = 'Teclado mecanico';
-- DELETE: eliminar solo lo que corresponde
DELETE FROM ventas_tecnologia
WHERE id_venta = 28;
Antes de ejecutar UPDATE o DELETE, convierte el mismo filtro en un SELECT y revisa las filas afectadas. Un DELETE sin WHERE puede borrar toda una tabla.
Consultas parametrizadas
Cuando el valor viene de una persona o de otra aplicacion, no lo concatentes en el SQL. Usa parametros:
import sqlite3
conn = sqlite3.connect(':memory:')
conn.execute('CREATE TABLE usuarios (id INTEGER, nombre TEXT)')
nombre_buscado = "Ana' OR 1=1 --"
query = 'SELECT * FROM usuarios WHERE nombre = ?'
filas = conn.execute(query, (nombre_buscado,)).fetchall()
El ? separa el codigo SQL del valor. Esta practica evita inyeccion SQL y conserva correctamente comillas, acentos y tipos.
Transacciones
Una transaccion permite tratar varias operaciones como una unidad atomica.
BEGIN TRANSACTION;
UPDATE cuentas SET saldo = saldo - 100 WHERE id_cuenta = 1;
UPDATE cuentas SET saldo = saldo + 100 WHERE id_cuenta = 2;
-- Si ambas operaciones son correctas:
COMMIT;
-- Si algo falla antes del commit:
-- ROLLBACK;
La idea es todo o nada: una transferencia no debe descontar dinero sin acreditarlo en la otra cuenta.
1.3 DCL y seguridad de datos
DCL significa Data Control Language. Administra quien puede leer o modificar cada recurso.
-- Sintaxis conceptual habitual en PostgreSQL/MySQL
GRANT SELECT ON ventas_tecnologia TO analista;
GRANT SELECT, INSERT ON ventas_tecnologia TO operador;
REVOKE INSERT ON ventas_tecnologia FROM analista;
SQLite no implementa usuarios y privilegios como un servidor de base de datos; protege principalmente el archivo y el acceso al sistema operativo. En produccion, PostgreSQL, BigQuery, Snowflake o SQL Server ofrecen controles mas completos.
| Concepto | Descripcion |
|---|---|
| DML | SELECT, INSERT, UPDATE, DELETE Trabaja con registros. |
| DCL | GRANT, REVOKE Trabaja con permisos. |
| DDL | CREATE, ALTER, DROP Trabaja con tablas y esquemas. |
1.4 Del modelo entidad-relacion a tablas
El modelo ER describe entidades y relaciones antes de escribir SQL.
Cardinalidades
| Relacion | Interpretacion | Implementacion |
|---|---|---|
| 1:1 | Una persona tiene un perfil. | FK con restriccion UNIQUE. |
| 1:N | Un cliente realiza muchas ventas. | FK en el lado N. |
| N:M | Productos aparecen en muchas ventas y ventas contienen muchos productos. | Tabla intermedia. |
Una relacion N:M se transforma asi:
CREATE TABLE pedidos (
id_pedido INTEGER PRIMARY KEY,
id_cliente INTEGER NOT NULL
);
CREATE TABLE productos (
id_producto INTEGER PRIMARY KEY,
nombre TEXT NOT NULL
);
CREATE TABLE pedido_producto (
id_pedido INTEGER NOT NULL,
id_producto INTEGER NOT NULL,
cantidad INTEGER NOT NULL CHECK (cantidad > 0),
PRIMARY KEY (id_pedido, id_producto),
FOREIGN KEY (id_pedido) REFERENCES pedidos(id_pedido),
FOREIGN KEY (id_producto) REFERENCES productos(id_producto)
);
La clave primaria compuesta evita repetir el mismo producto dentro del mismo pedido.
💡 Piensa en el lado que puede repetirse.
Dependencias funcionales
Una dependencia funcional expresa que una columna determina otra:
id_producto → nombre_producto, categoria, precio_lista
id_cliente → nombre_cliente, segmento
Si guardas nombre_producto repetido en todas las ventas, cualquier cambio de nombre requiere actualizar muchas filas. La normalizacion busca que cada hecho se guarde en el lugar donde realmente depende.
1.5 Normalizacion basica
Normalizar busca reducir redundancia y dependencias incorrectas.
Primera forma normal (1FN)
Cada celda debe contener un valor atomico. Esto es dificil de consultar:
cliente_id | telefonos
1 | 555-111, 555-222
Una estructura mejor separa los telefonos:
cliente_id | telefono
1 | 555-111
1 | 555-222
Segunda y tercera forma normal
- 2FN: cumple 1FN y cada atributo no clave depende de toda la clave primaria, no solo de una parte de una clave compuesta.
- 3FN: cumple 2FN y los atributos no clave no dependen de otros atributos no clave.
Ejemplo: si una tabla ventas tiene id_venta, id_producto, nombre_producto y categoria, el nombre y la categoria dependen del producto, no de la venta. Separarlos en productos evita una dependencia transitiva.
Dependencia incorrecta
Si precio_producto depende de id_producto y no de cada venta, guardar el precio repetido en muchas filas puede crear inconsistencias. La tabla de productos debe ser la fuente del precio vigente y la venta debe conservar el precio historico cobrado cuando el negocio lo necesita.
Normaliza para proteger consistencia y evitar actualizaciones contradictorias. Luego decide si una capa analitica necesita un modelo dimensional o una tabla derivada para consultar mas rapido. La base operacional y el modelo para BI no siempre tienen la misma forma.
1.6 SQLite: base local para practicar
SQLite es un motor relacional embebido. No necesita servidor y guarda todo en un archivo .db o en memoria.
import sqlite3
import pandas as pd
conn = sqlite3.connect(':memory:')
ventas = pd.read_csv('data/ventas_tecnologia.csv')
ventas.to_sql('ventas_tecnologia', conn, index=False)
query = """
SELECT categoria,
SUM(precio_unitario * cantidad) AS ingresos_totales
FROM ventas_tecnologia
GROUP BY categoria
ORDER BY ingresos_totales DESC;
"""
resultado = pd.read_sql_query(query, conn)
print(resultado)
El puente importante es:
CSV → DataFrame → tabla SQLite → SQL → DataFrame → modelo
SQLite
M1Motor relacional embebido y sin servidor. Es ideal para practicar, prototipar y construir aplicaciones locales pequeñas.
Ej: sqlite3.connect(':memory:') crea una base temporal que vive mientras el proceso esta abierto.
Inspeccionar y parametrizar SQLite
schema = conn.execute("PRAGMA table_info(ventas_tecnologia)").fetchall()
for column in schema:
print(column[1], column[2], "NOT NULL" if column[3] else "nullable")
pais = 'Colombia'
resultado = pd.read_sql_query(
'SELECT producto, cantidad FROM ventas_tecnologia WHERE pais = ?',
conn,
params=(pais,),
)
PRAGMA table_info convierte el esquema en informacion verificable. Los parametros mantienen segura la consulta y permiten reutilizarla con distintos valores.
1.7 Herramientas distribuidas en Python
Cuando el dataset ya no cabe en memoria o el procesamiento necesita varios nodos, aparecen herramientas distribuidas.
| Herramienta | Abstraccion | Cuando usarla |
|---|---|---|
| Pandas | DataFrame en memoria | Dataset manejable en una maquina. |
| Polars | DataFrame rapido y expresiones lazy | Transformaciones locales con alto rendimiento. |
| Dask | DataFrame/arrays particionados | Escalar APIs parecidas a Python en varios procesos. |
| Spark | RDD/DataFrame distribuido | Grandes volumenes y clusters. |
| DuckDB | SQL analitico embebido | Consultar CSV/Parquet localmente con SQL. |
La herramienta no reemplaza el razonamiento. Antes de distribuir, mide el problema: volumen, memoria, tiempo, costo y complejidad operativa.
Particiones, lazy execution y costo
Tres ideas aparecen cuando escalas:
- Particion: fragmento de datos que puede procesarse o leerse de manera independiente.
- Lazy execution: las transformaciones se describen primero y se ejecutan cuando se solicita el resultado; permite optimizar el plan completo.
- Shuffle: movimiento de datos entre particiones para agrupar o unir; suele ser una de las operaciones mas costosas.
Antes de agregar un cluster, revisa si el problema es un SELECT *, un join sin filtro, una particion desbalanceada o una transformacion innecesaria.
1.8 Bases de datos modernas y ML
Las bases actuales se diferencian por el tipo de carga que optimizan:
| Tipo | Prioridad | Ejemplos |
|---|---|---|
| OLTP | Muchas operaciones pequenas y consistentes. | PostgreSQL, MySQL, SQLite. |
| OLAP | Consultas analiticas sobre grandes volumenes. | BigQuery, Snowflake, DuckDB. |
| Vectorial | Buscar por similitud entre embeddings. | pgvector, Pinecone, Weaviate. |
| Documental | Documentos flexibles y jerarquicos. | Firestore, MongoDB. |
Para ML, la pregunta no es “cual es la base mas moderna”, sino:
- ¿Que volumen y frecuencia tienen los datos?
- ¿La latencia es operacional o analitica?
- ¿Necesitamos joins relacionales o documentos flexibles?
- ¿Hay embeddings y busqueda semantica?
- ¿Como reproducimos el dataset usado para entrenar?
Arquitectura por capas
Una separacion habitual es:
Fuente operacional
↓
Ingesta / eventos
↓
Data lake o almacenamiento crudo
↓
Transformaciones y validaciones
↓
Data warehouse / feature store
↓
Notebook, dashboard o servicio de ML
Cada capa tiene una responsabilidad. Si transformas datos directamente en un notebook y no guardas la consulta ni la fecha de corte, no puedes reconstruir el dataset que produjo una prediccion.
Introducción a Machine Learning
Machine Learning es una forma de construir reglas de prediccion o decision a partir de ejemplos. En lugar de escribir manualmente todas las condiciones, entregamos datos, definimos un objetivo y evaluamos si el modelo generaliza a observaciones que no vio durante el entrenamiento.
El esquema minimo de un problema supervisado es:
datos historicos
↓
features (X) + target (y)
↓
entrenamiento del modelo
↓
predicciones sobre datos nuevos
↓
metrica tecnica + decision de negocio
Tres preguntas deben quedar respondidas antes de entrenar:
- ¿Que representa una fila? Cliente, venta, producto, sesion u otra unidad de observacion.
- ¿Que queremos estimar? El
target, con una definicion operativa y un horizonte temporal. - ¿Como sabremos si funciona? Una metrica de evaluacion conectada con una decision real.
El modelo no reemplaza la base de datos ni la calidad del dato. Aprende de la representacion que recibe; por eso un target mal definido, una feature duplicada o una fecha futura filtrada durante el entrenamiento puede producir una solucion aparentemente precisa pero inutil en produccion.
1.9 Preparacion de datos para Machine Learning
El modelo necesita una matriz de features X y un vector objetivo y.
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
X = datos[['precio_unitario', 'cantidad']]
y = datos['categoria_objetivo']
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
El scaler se ajusta solo con entrenamiento. Si aprende parametros del test, aparece data leakage.
Features, target y unidad de observacion
Antes de construir X e y, escribe la unidad de observacion:
Una fila = un cliente al cierre de cada mes
Target = cancela_en_los_proximos_30_dias
Features= compras_ultimos_90_dias, tickets_abiertos, saldo_pendiente
Si mezclas ventas individuales con clientes mensuales, puedes duplicar observaciones y entregar al modelo informacion del futuro. La granularidad debe ser la misma en la tabla de features y en el target.
Split temporal
En problemas que predicen el futuro, un split aleatorio puede ser engañoso. Una alternativa es:
Entrenamiento: enero a septiembre
Validacion: octubre
Test: noviembre
El orden temporal simula la operacion real: entrenar con pasado y predecir datos que aun no existian.
Tipos de aprendizaje
| Tipo | Tiene target | Ejemplo |
|---|---|---|
| Supervisado | Si | Predecir churn o precio. |
| No supervisado | No | Agrupar clientes. |
| Semi-supervisado | Parcialmente | Muchas filas sin etiqueta y pocas etiquetadas. |
| Por refuerzo | Recompensa | Elegir acciones para maximizar una recompensa. |
Data leakage
M1Situacion en la que informacion del futuro o del conjunto de validacion entra al entrenamiento y produce una evaluacion artificialmente optimista.
Ej: Escalar todo el dataset antes de separar train y test.
1.10 Integracion: pipeline reproducible
Un pipeline minimo debe separar extraccion, validacion, transformacion y entrenamiento.
def cargar_datos(path):
return pd.read_csv(path)
def validar_datos(df):
columnas_requeridas = {'precio_unitario', 'cantidad'}
faltantes = columnas_requeridas - set(df.columns)
if faltantes:
raise ValueError(f'Faltan columnas: {faltantes}')
if (df['cantidad'] <= 0).any():
raise ValueError('La cantidad debe ser positiva')
return df
datos = cargar_datos('data/ventas_tecnologia.csv')
datos = validar_datos(datos)
La validacion es parte del producto. Si solo funciona cuando el creador recuerda los pasos exactos, no es reproducible.
Contrato, linaje e idempotencia
Un pipeline profesional deja tres evidencias:
- Contrato de datos: columnas, tipos, rangos y reglas esperadas.
- Linaje: de que fuente provino cada columna y que transformaciones recibio.
- Idempotencia: ejecutarlo dos veces con la misma entrada produce el mismo resultado, sin duplicar registros.
def validar_contrato(df):
esperado = {
'precio_unitario': 'float64',
'cantidad': 'int64',
}
for columna, dtype in esperado.items():
if columna not in df:
raise ValueError(f'Falta la columna {columna}')
if str(df[columna].dtype) != dtype:
raise TypeError(f'{columna}: se esperaba {dtype}, llego {df[columna].dtype}')
El contrato falla temprano y evita que un cambio silencioso en el origen llegue al modelo.
- Define las tablas clientes, productos y ventas.
- Marca claves primarias y foraneas.
- Escribe una consulta que construya una feature mensual por cliente.
- Valida nulos y tipos antes de pasar el resultado a Pandas.
- Explica donde podria aparecer leakage temporal.
1.11 Preguntas de entrevista
Q: ¿Por que normalizar una base?
Para reducir redundancia, proteger consistencia y evitar dependencias incorrectas. Luego podemos construir tablas derivadas o un star schema para consultas analiticas.
🪤 Decir que normalizar siempre hace las consultas mas rapidas.Q: ¿Por que ajustas el scaler solo con train?
Porque los parametros del transformador deben representar solo la informacion disponible durante el entrenamiento. Usar test filtra informacion del futuro y sesga la evaluacion.
🪤 Usar fit_transform sobre todo el dataset antes de separar.