Saltar al contenido
BásicoArturo Lorenzo·12 de noviembre de 2026·8 min·13lecturas

Data lake o data warehouse: los mismos pedidos de una tienda guardados de dos formas

Los mismos pedidos: a la izquierda, cada uno con su forma, flotando en el lago; a la derecha, todos encajados en las mismas columnas.

Tienes un montón de datos y dos sitios donde guardarlos: uno te deja tirarlo todo tal cual llega, y el otro te obliga a ordenarlo antes de entrar. Nadie nace sabiendo cuál conviene, y en las ofertas de empleo verás los dos nombres a todas horas: data lake y data warehouse. Vamos a guardar los mismos pedidos de las dos formas para que veas qué cambia.

## El ejemplo: cinco pedidos de una tienda que no existe

Una tienda online ficticia, con nombres e importes inventados para este post. Hoy ha recibido cinco pedidos. El segundo trae un dato extra, un cupón de descuento, y al quinto se le ha olvidado el importe.

  • Pedido 1001: Lucía, 38 €
  • Pedido 1002: Marcos, 15 €, con el cupón OTOÑO
  • Pedido 1003: Lucía, 12,5 €
  • Pedido 1004: Elena, 30 €
  • Pedido 1005: Pablo, sin importe

Los pedidos son los mismos en los dos lados, y el total de los completos es 95,5 €. Lo único que cambia es cómo los guardamos y, sobre todo, cuándo decidimos qué forma tienen.

## Data lake: todo tal cual, en ficheros

Un data lake (lago de datos) es un sitio donde guardas los datos exactamente como llegan, sin tocarlos. Normalmente son ficheros (archivos sueltos, como un .json o un .csv) en una carpeta, a menudo en la nube (es decir, en ordenadores de otra empresa a los que accedes por internet, en vez de en un disco tuyo). Piensa en el cajón de los cables de tu casa: cabe de todo, no preguntas qué es cada cosa al meterla, y lo ordenas el día que necesitas uno.

En nuestra tienda, cada pedido es un fichero. El de Marcos tiene el campo `cupon` y los demás no; el de Pablo no tiene importe. Al lago le da igual. Un esquema es la descripción de qué campos tiene un dato y de qué tipo es cada uno (por ejemplo, "importe: número"). En el lago el esquema se decide al leer: lo que en inglés se llama schema-on-read (esquema al leer). Cuando alguien quiere el total, abre los ficheros y coge de cada uno el campo que le interesa.

En el lago los ficheros entran sin pasar control; el orden llega después, cuando alguien los mira con lupa para sacar una respuesta.

La gran ventaja: guardas todo, también lo que hoy no sabes para qué servirá. Nadie te rechaza un fichero raro. El peligro es el mismo cajón de los cables sin etiquetas: sin cuidado, nadie sabe qué hay dentro y el lago se convierte en un pantano de datos (*data swamp*), un sitio donde hay de todo y no se encuentra nada.

## Data warehouse: una tabla donde todo encaja

Un data warehouse (almacén de datos) guarda los datos en tablas: filas y columnas, cada columna con su nombre y su tipo (número, texto, fecha). Piensa en una estantería de archivo con cajones etiquetados: cada papel va en su sitio, y encontrarlo es rápido. Es un tipo de base de datos, un programa que guarda datos de forma organizada y responde preguntas sobre ellos. Se consulta con SQL, el lenguaje de las tablas. En el mundo real verás nombres como BigQuery, Snowflake o Redshift.

Aquí el esquema (qué columnas hay y de qué tipo es cada una) se decide al escribir: schema-on-write, esquema al escribir. Antes de guardar un pedido, la tabla ya sabe qué columnas tiene, y si un pedido no encaja, no entra. Lo normal es que alguien limpie y coloque los datos antes de cargarlos. Ese paso se llama ETL: Extract, Transform, Load, es decir, extraer los datos, transformarlos y cargarlos. Existe la versión inversa, ELT, que carga primero y transforma después dentro del propio destino, y la verás mucho en las ofertas.

La tabla tiene puerta: el pedido completo pasa y encaja en sus columnas; el que viene sin importe se queda fuera.

## Los mismos pedidos, en código

Esto lo practicamos en profundidad en la plataforma, en los módulos de data lakes y de data warehouse. Aquí lo simulamos con lo que trae Python. Para el lago, cada pedido se escribe como un fichero JSON (un formato de texto para datos, con pares nombre-valor) en una carpeta temporal, una carpeta que Python crea al empezar y borra sola al terminar. Para el almacén usamos `sqlite3`, una base de datos que viene con Python: no es un warehouse de verdad, pero nos sirve de tabla de juguete, y vive en memoria (en la RAM del ordenador, sin crear ningún fichero), así que desaparece al acabar. Por eso puedes ejecutarlo las veces que quieras: cada vez empieza de cero. Para probarlo, comprueba que tienes Python con `python3 --version`, copia el código en un fichero `pedidos.py` y ejecuta `python3 pedidos.py`.

1import json
2import sqlite3
3import tempfile
4from pathlib import Path
5
6pedidos = [
7 {"id": 1001, "cliente": "Lucía", "importe": 38.0},
8 {"id": 1002, "cliente": "Marcos", "importe": 15.0, "cupon": "OTOÑO"},
9 {"id": 1003, "cliente": "Lucía", "importe": 12.5},
10 {"id": 1004, "cliente": "Elena", "importe": 30.0},
11 {"id": 1005, "cliente": "Pablo"},
12]
13
14# LAKE: cada pedido se guarda tal cual llega, un fichero por pedido
15with tempfile.TemporaryDirectory() as carpeta:
16 lago = Path(carpeta)
17 for p in pedidos:
18 ruta = lago / f"pedido_{p['id']}.json"
19 ruta.write_text(json.dumps(p, ensure_ascii=False), encoding="utf-8")
20 print("LAKE:", len(list(lago.glob("*.json"))), "ficheros, ninguno rechazado")
21
22 # La estructura se decide al leer
23 leidos = [json.loads(f.read_text(encoding="utf-8")) for f in sorted(lago.glob("*.json"))]
24 for p in leidos:
25 if "cupon" in p:
26 print(" cupón de", p["cliente"], ":", p["cupon"])
27 totales = {}
28 for p in leidos:
29 totales[p["cliente"]] = totales.get(p["cliente"], 0.0) + p.get("importe", 0.0)
30 for cliente in sorted(totales):
31 print(" ", cliente, totales[cliente], "€")
32 print(" total:", sum(totales.values()), "€")
33
34# WAREHOUSE: una tabla con columnas fijas
35con = sqlite3.connect(":memory:")
36con.execute("CREATE TABLE pedidos (id INTEGER PRIMARY KEY, cliente TEXT NOT NULL, importe REAL NOT NULL)")
37print("WAREHOUSE:")
38for p in pedidos:
39 fila = {"id": p["id"], "cliente": p["cliente"], "importe": p.get("importe")}
40 try:
41 con.execute("INSERT INTO pedidos VALUES (:id, :cliente, :importe)", fila)
42 except sqlite3.IntegrityError as e:
43 print(" rechazado el pedido", p["id"], "->", e)
44for cliente, total in con.execute("SELECT cliente, SUM(importe) FROM pedidos GROUP BY cliente ORDER BY cliente"):
45 print(" ", cliente, total, "€")
46print(" total:", con.execute("SELECT SUM(importe) FROM pedidos").fetchone()[0], "€")

Salida: en el lago, "5 ficheros, ninguno rechazado", el cupón de Marcos (OTOÑO) y el total por cliente (Elena 30.0 €, Lucía 50.5 €, Marcos 15.0 €, Pablo 0.0 €), con total 95.5 €. En el warehouse, "rechazado el pedido 1005 -> NOT NULL constraint failed: pedidos.importe", el mismo total por cliente sin Pablo y total 95.5 €.

Primero, lo de Python. `Path` representa una carpeta o un fichero, y `lago / "x.json"` pega un nombre a la carpeta. `with` abre la carpeta temporal y la cierra al salir del bloque. `f"pedido_{p['id']}.json"` es una f-string: un texto donde lo que va entre llaves se sustituye por su valor. `glob("*.json")` lista los ficheros que acaban en .json, y `json.loads` convierte el texto de un fichero en un diccionario de Python. La línea con corchetes y `for` dentro es una comprensión de lista: una forma corta de construir una lista recorriendo otra. Y `p.get("importe", 0.0)` devuelve 0.0 si el campo no existe, en vez de dar error.

Y ahora, el SQL, una línea por cada pieza. `CREATE TABLE` crea la tabla; `PRIMARY KEY` marca la columna que identifica cada fila y no puede repetirse; `NOT NULL` exige que la columna no quede vacía. `INSERT` añade una fila. `SUM` suma una columna, `GROUP BY cliente` agrupa las filas por cliente, y `ORDER BY` las ordena. Ahora compara los dos lados. Los cinco pedidos entran en el lago, el cupón de Marcos sigue ahí y el pedido de Pablo es un fichero más, sin quejas. En la tabla, el cupón no tiene columna y se descarta, y el pedido 1005 salta como `IntegrityError`, el error que lanza la base de datos cuando una fila rompe una regla de la tabla. El total es 95,5 € en los dos casos.

Ese cupón que la tabla descartó era un dato real. Si lo hubieras guardado solo en el warehouse, ya no lo tendrías. Es la razón por la que muchos equipos conservan el original en un lago aunque luego trabajen con tablas.

## Cuál elijo yo

PreguntaData lakeData warehouse
Qué guardaFicheros tal cualTablas ordenadas
Cuándo se da formaAl leerAl guardar
Qué pasa con un dato raroEntra igualSe rechaza o se limpia
Cómo se consultaNormalmente con códigoCon SQL sobre tablas
Riesgo típicoPantano de datosPerder lo que no encaja
Los mismos pedidos de la tienda ficticia, vistos desde cada lado.

Mi postura: si tu jefa te va a preguntar "cuánto hemos vendido por cliente", la respuesta tiene que salir de una tabla. Un lago es un buen sitio donde guardar, no un buen sitio donde preguntar: imagina que en vez de cinco fueran cuatro mil ficheros, ¿quién querría abrirlos uno a uno para sumar una columna? Por eso en las empresas reales conviven los dos: el lago guarda el original y el warehouse sirve las respuestas, y entre ambos hay un proceso de ETL o ELT que lleva los datos de uno a otro. Es un reparto de tareas.

Cuando en una entrevista te pregunten la diferencia, no recites definiciones: cuenta el ejemplo de los pedidos. "En el lago guardo el pedido tal cual, con su cupón; en el warehouse lo coloco en columnas y pierdo lo que no encaja. Así guardo en uno y pregunto al otro." Se entiende en diez segundos.

En resumen: el data lake guarda los datos tal cual en ficheros y les da forma al leerlos; el data warehouse los guarda ordenados en tablas y les da forma al escribirlos. Mismos pedidos, mismo total, dos formas de llegar. Para practicar, añade un campo nuevo, como `"regalo": True`, a uno de los pedidos de la lista `pedidos`, vuelve a ejecutar el script y comprueba qué hacen el lago y la tabla. Luego pregúntate: ¿qué columna habría que añadir al warehouse para no perderlo?

$ whoami

Regístrate gratis para dar like, guardar posts en carpetas y recibir nuevos artículos por email.

Leer está bien. Ejecutar está mejor.

## comentarios

¿Te ha servido? ¿Añadirías algo? ¿Lo has vivido de otra forma en tu trabajo? Cuéntalo.

Inicia sesión para comentar y responder.

cargando comentarios...