Módulo 3: Star Vs Snowflake Vs One Big Table
Presentación del módulo: star vs snowflake vs One Big Table
Por qué existe este módulo
El módulo 2 cerró con una frase que quedó pendiente a propósito, en la última línea de su propio proyecto: "el módulo 3 necesita, como punto de partida, exactamente el star schema que este proyecto acaba de cerrar". Ese star —fact_orders unido a dim_store, dim_product y dim_date mediante tres JOIN verificados, cuarenta filas antes, cuarenta después— ya existe. Este módulo no lo reemplaza. Lo pone a prueba.
Hasta ahora, esta guía construyó el star schema como si fuera la única forma razonable de modelar un warehouse analítico, y en cierto sentido lo es: Kimball lo recomienda por defecto, y los módulos 1 y 2 explicaron con detalle por qué. Pero un modelador con criterio no se queda con "la forma recomendada por defecto" sin entender también sus alternativas y sus costos reales. dim_product, tal como la dejó el módulo 2, tiene una columna category de texto —"beverages", "snacks", "electronics"— repetida en cada producto que pertenece a esa categoría. ¿Eso es un problema? Depende de la pregunta que le hagas. Si mañana Kiosko decide renombrar "beverages" a "drinks", ¿cuántas filas hay que tocar? Y en el otro extremo: si el equipo de BI de Kiosko quiere un tablero que responda preguntas de ventas sin escribir un solo JOIN, ¿qué tabla le entregas?
Este módulo responde ambas preguntas con evidencia, no con una regla fija. Primero normaliza —saca category de dim_product a su propia tabla, dim_category, construyendo por primera vez en esta guía un snowflake schema—. Después compara el costo real de un JOIN entre el star y el snowflake, usando EXPLAIN para ver el plan de ejecución, no solo para imaginarlo. Y en la dirección exactamente opuesta, construye una tabla ancha completamente denormalizada —mart_daily_sales_obt, un "One Big Table" (OBT) real, con quince columnas y cero JOIN pendientes— y mide, con números literales, cuánto cuesta esa comodidad en espacio y en mantenimiento. Al cerrar el módulo, vas a poder defender con evidencia cuándo cada una de las tres formas gana, en vez de elegir por moda o por costumbre.
Conexión con el módulo. Este módulo no toca fact_orders, ni el star que construiste en el módulo 2 —siguen exactamente igual, sirviendo de línea base contra la que se comparan las otras dos formas—. Lo que construye son dos estructuras nuevas y paralelas: la versión snowflake (dim_category + dim_product_normalized) y la versión OBT (mart_daily_sales_obt), ambas verificadas contra el mismo revenue de siempre: 106.15.
Una analogía: el clóset, las cajas etiquetadas, y la mesa ya servida
Retoma el clóset del módulo 2. El star schema era el clóset ordenado: cada prenda a la mano, un solo movimiento para llegar a cualquier cosa. Este módulo agrega dos formas más a la comparación.
La primera es la que el módulo 2 ya nombró sin construir: el snowflake schema son las cajas etiquetadas dentro de otras cajas. En vez de tener "categoría" como una etiqueta visible en cada prenda, guardas las prendas por tipo en cajas —"bebidas", "snacks", "electrónica"— y esas cajas, a su vez, tienen su propio índice en una lista aparte. Si algún día renombras la caja "bebidas" a "líquidos", solo tocas una etiqueta —la de la caja—, no cada prenda individual que hay dentro. Ganas consistencia y facilidad de mantenimiento; pagas con un paso adicional cada vez que quieres saber a qué categoría pertenece una prenda específica: primero encuentras la prenda, después vas a buscar en qué caja está, después lees la etiqueta de la caja.
La segunda forma es nueva en este módulo: la tabla ancha, One Big Table (OBT), es la mesa ya servida. En vez de un clóset —ordenado o con cajas anidadas, da igual— del que hay que sacar cada ingrediente por separado, imagina que alguien ya preparó el plato completo: la proteína, el acompañamiento, la salsa, todo en un solo plato, listo para comer sin ir a la cocina ni una sola vez. Para quien tiene hambre y quiere comer ya, es la opción más rápida posible. El costo aparece en otro lugar: preparar ese plato tomó tiempo antes de que llegara el comensal, y si mañana alguien decide que la salsa debía ser otra, hay que rehacer el plato completo —no alcanza con cambiar un frasco en la despensa—. Ese es, exactamente, el trade-off que este módulo va a medir con números reales: la OBT le ahorra el JOIN a quien consulta, a cambio de repetir, en cada plato servido, ingredientes que en el clóster ordenado —o en las cajas anidadas— vivían en un solo lugar.
Ejemplo trabajado: las tres formas, antes de construirlas
Antes de escribir la primera línea de SQL de este módulo, vale la pena ver, de un vistazo, qué construye cada lección y en qué se diferencian las tres formas que vas a comparar.
# three_shapes_map.py
SHAPES = [
("star", "El heredado del modulo 2: fact_orders + 3 JOIN. category vive como texto dentro de dim_product."),
("snowflake", "Normaliza category en su propia tabla: dim_category + dim_product_normalized. Un salto de JOIN mas para llegar a category."),
("OBT (tabla ancha)", "mart_daily_sales_obt: cero JOIN pendientes. Cada fila ya trae todo -- fecha, tienda, producto, categoria -- junto."),
]
COMPONENTS = [
("Normalizar una dimension", "dim_category + dim_product_normalized, sin perder ningun producto"),
("Comparar el costo del JOIN", "EXPLAIN: 1 salto (star) vs 2 saltos (snowflake), plan de ejecucion real"),
("El argumento moderno de la OBT", "Por que el warehouse columnar cambio el calculo clasico de normalizar"),
("Construir la OBT de Kiosko", "mart_daily_sales_obt: 15 columnas, cero JOIN, mismo revenue de siempre"),
("Cuando el snowflake todavia gana", "El costo real de actualizar un valor repetido -- medido, no supuesto"),
("Cuando la OBT todavia gana", "La misma pregunta de negocio, resuelta 3 veces, mismo resultado"),
("El star, snowflake y OBT comparados", "SHAPE_COMPARISON: la declaracion formal que cierra el modulo"),
]
print("=== Las tres formas que este modulo compara ===\n")
for name, description in SHAPES:
print(f"- {name}")
print(f" {description}\n")
print("=== Las siete piezas que construyen esa comparacion ===\n")
for i, (name, description) in enumerate(COMPONENTS, start=1):
print(f"{i}. {name}")
print(f" {description}\n")
Qué esperar. Al correr python3 three_shapes_map.py, la salida es exactamente esta:
=== Las tres formas que este modulo compara ===
- star
El heredado del modulo 2: fact_orders + 3 JOIN. category vive como texto dentro de dim_product.
- snowflake
Normaliza category en su propia tabla: dim_category + dim_product_normalized. Un salto de JOIN mas para llegar a category.
- OBT (tabla ancha)
mart_daily_sales_obt: cero JOIN pendientes. Cada fila ya trae todo -- fecha, tienda, producto, categoria -- junto.
=== Las siete piezas que construyen esa comparacion ===
1. Normalizar una dimension
dim_category + dim_product_normalized, sin perder ningun producto
2. Comparar el costo del JOIN
EXPLAIN: 1 salto (star) vs 2 saltos (snowflake), plan de ejecucion real
3. El argumento moderno de la OBT
Por que el warehouse columnar cambio el calculo clasico de normalizar
4. Construir la OBT de Kiosko
mart_daily_sales_obt: 15 columnas, cero JOIN, mismo revenue de siempre
5. Cuando el snowflake todavia gana
El costo real de actualizar un valor repetido -- medido, no supuesto
6. Cuando la OBT todavia gana
La misma pregunta de negocio, resuelta 3 veces, mismo resultado
7. El star, snowflake y OBT comparados
SHAPE_COMPARISON: la declaracion formal que cierra el modulo
Todavía ningún número real de Kiosko —este mapa es, otra vez, el plano antes de la construcción—. Pero fíjate en el orden: primero normalizas (construyes el snowflake), después mides el costo de esa normalización con EXPLAIN, después vas al extremo contrario y entiendes por qué alguien elegiría no normalizar nada, después construyes esa tabla ancha de verdad, y solo al final —con las tres formas ya construidas y verificadas— comparas cuándo cada una gana. No hay atajos: no puedes argumentar con criterio sobre un trade-off que no mediste tú mismo.
Diagrama: dónde estabas, dónde vas a estar
flowchart LR
subgraph M2["Modulo 2 (ya escrito)"]
A["Star schema completo\nfact_orders + 3 JOIN\ndim_product.category = texto"]
end
subgraph M3["Este modulo (3 de 8)"]
B["L2: dim_category\n(normalizado, EJECUTADO)"]
C["L3: EXPLAIN\n1 salto vs 2 saltos, EJECUTADO"]
D["L4: El argumento OBT\n(conceptual + numeros)"]
E["L5: mart_daily_sales_obt\n(EJECUTADO, 15 columnas)"]
F["L6-L7: Cuando gana cada forma\n(EJECUTADO, costo de update)"]
G["L8: Las tres formas\ncomparadas, EJECUTADO"]
end
subgraph Resto["Modulos 4-8"]
H["SCD, join punto-en-el-tiempo,\naccumulating snapshot..."]
end
A --> B --> C --> D --> E --> F --> G --> H
El mapa de este módulo
Leccion Que construye
──────── ──────────────────────────────────────────────────────────────
L1 (esta) El mapa: las tres formas, antes de construirlas
L2 Normalizando una dimension: dim_category, EJECUTADO
L3 Comparando el costo de un JOIN con EXPLAIN, EJECUTADO
L4 El argumento de la tabla ancha (One Big Table), conceptual
L5 Construyendo mart_daily_sales_obt, EJECUTADO
L6 Cuando el snowflake todavia gana, EJECUTADO (costo de update)
L7 Cuando la OBT todavia gana, EJECUTADO (misma pregunta, 3 caminos)
L8 Proyecto: las tres formas de Kiosko comparadas
Las lecciones 2, 3 y 5 son las que ejecutan la mayor parte del código nuevo de este módulo: construir el snowflake, comparar su plan de ejecución contra el star, y construir la OBT. La lección 4 es mayormente conceptual —el argumento moderno detrás de la tabla ancha, apoyado en benchmarks reales de la industria—, aunque también corre un ejemplo pequeño. Las lecciones 6 y 7 son las que le dan criterio a todo lo anterior: cada una ejecuta una comparación concreta que muestra, con números, cuándo cada forma gana. La lección 8 cierra con el mini-proyecto: las tres formas construidas a la vez, verificadas contra el mismo revenue de 106.15.
Profundización: por qué esta comparación necesita el star ya construido
Podría parecer que este módulo podría haberse escrito antes del módulo 2 —al final, "comparar formas de modelar" suena como una discusión de diseño, no como algo que dependa de tener ya un star schema funcionando—. Este módulo resiste esa tentación por una razón concreta: no se puede medir el costo de un JOIN extra si no existe, ya construido y verificado, el JOIN de referencia contra el que se compara. El star del módulo 2 —fact_orders unido a dim_product en un solo salto— es exactamente esa referencia. Sin él, la lección 3 de este módulo no tendría contra qué comparar el plan de EXPLAIN de la versión snowflake; sin el revenue ya verificado del módulo 2 (106.15), la lección 5 no tendría cómo confirmar que construir la OBT no alteró ni un centavo del hecho original.
Hay una segunda razón, más sutil: esta guía enseña a decidir la forma de un modelo con evidencia, no con una preferencia declarada de antemano. Eso solo es posible si, para cada forma que se compara, existe un ejemplo real, ejecutado, verificable — no un diagrama hipotético. El módulo 2 construyó esa base real. Este módulo la usa como punto de partida fijo, y construye dos variaciones —más normalizada, menos normalizada— sobre exactamente el mismo dato, para que la comparación sea justa: los mismos cuarenta pedidos, las mismas tres tiendas, los mismos cuatro productos, en las tres formas.
Errores comunes
Pensar que este módulo reemplaza el star schema del módulo 2 por una forma "mejor". Qué pasa: alguien, al ver que este módulo construye una versión snowflake y una versión OBT, asume que una de las dos va a "ganar" y reemplazar al star como la forma canónica del resto de la guía. Por qué pasa: es tentador esperar que un módulo titulado "star vs snowflake vs OBT" termine con un veredicto único y definitivo. Cómo detectarlo: si al terminar este módulo esperas que el módulo 4 (SCD) historice dim_product_normalized o mart_daily_sales_obt en vez del dim_product original, tienes esta confusión. Cómo corregirlo: el star del módulo 2 sigue siendo, durante el resto de esta guía, la base canónica sobre la que se construye todo lo demás —SCD, joins punto-en-el-tiempo, accumulating snapshot—. El snowflake y la OBT de este módulo son comparaciones paralelas, construidas para entender el trade-off, no reemplazos que sobreviven más allá de este módulo.
Asumir que "normalizar es siempre más correcto" o que "denormalizar es siempre más rápido", sin medir. Qué pasa: alguien llega a este módulo con una opinión ya formada —quizás de otra experiencia, otro curso, otro trabajo— sobre cuál de las tres formas es "la buena", y espera que este módulo simplemente confirme esa opinión. Por qué pasa: el debate normalizar-vs-denormalizar tiene décadas, y casi todo el mundo llega con un bando ya elegido. Cómo detectarlo: si terminas la lección 3 sorprendido por el resultado real de EXPLAIN, o la lección 5 sorprendido por lo que realmente cuesta —en espacio, en filas para actualizar— la tabla ancha, es señal de que tu opinión previa no estaba respaldada por evidencia medida sobre este dato específico. Cómo corregirlo: las lecciones 6 y 7 de este módulo existen exactamente para esto — muestran, con números ejecutados, un caso concreto donde cada forma gana. Ninguna de las tres es universalmente superior; el contexto decide.
Saltarse la lección 4 porque "es solo teoría, sin código nuevo". Qué pasa: alguien, impaciente por llegar a la lección 5 (donde se construye la OBT de verdad), lee por encima la lección 4 y se salta el argumento que explica por qué la industria ha vuelto a considerar seriamente las tablas anchas en los últimos años. Por qué pasa: una lección sin una tabla nueva de DuckDB se siente menos "importante" que una que sí construye algo. Cómo detectarlo: si en la lección 7 no puedes explicar con tus propias palabras por qué el almacenamiento columnar cambió el cálculo clásico de "normalizar siempre ahorra espacio", te faltó la lección 4. Cómo corregirlo: la lección 4 no es un relleno — es el argumento que explica por qué este módulo no termina simplemente recomendando el star y descartando la OBT como una moda pasajera.
Ejercicios
Ejercicio 1 — Recuerda la fila exacta del checklist que este módulo resuelve. Sin releer el proyecto del módulo 2, escribe de memoria el nombre exacto de la fila del checklist (introducido en el módulo 1) que corresponde a este módulo.
Ver solución
La fila dice, textualmente: "Snowflake vs tabla ancha". A diferencia del módulo 2 —que resolvió tres piezas combinadas en una sola fila del checklist (llaves sustitutas, dim_date, dimensiones conformadas)—, este módulo resuelve una fila que ya nombra explícitamente las dos formas alternativas que va a construir y comparar contra el star: la versión normalizada (snowflake) y la versión denormalizada (tabla ancha, OBT).
Ejercicio 2 — Ordena las tres formas de más normalizada a menos normalizada. Sin mirar el ejemplo trabajado, ordena star, snowflake y OBT de la forma con menos duplicación de datos a la forma con más duplicación de datos.
Ver solución
De menos a más duplicación: snowflake (category vive en una sola tabla, dim_category, referenciada por llave) → star (category vive como texto dentro de dim_product, repetida una vez por producto — pero dim_product sigue siendo una tabla pequeña, separada de fact_orders) → OBT (todos los atributos de tienda, producto y fecha se repiten en cada fila de mart_daily_sales_obt, la forma con más duplicación de las tres). El snowflake es la forma más normalizada porque separa explícitamente la categoría en su propia tabla; la OBT es la menos normalizada porque junta todo en una sola fila ancha, sin ninguna tabla de dimensión aparte.
Ejercicio 3 — Explica la analogía de la mesa ya servida con tus propias palabras. Usando la analogía de esta lección (clóset ordenado = star, cajas etiquetadas dentro de cajas = snowflake, mesa ya servida = OBT), explica en 2-3 frases qué gana y qué pierde alguien que elige comer de la mesa ya servida en vez de ir a la cocina por cada ingrediente.
Ver solución
Quien come de la mesa ya servida gana velocidad inmediata: no tiene que ir a la cocina, buscar cada ingrediente por separado y combinarlos — el plato ya está completo y listo. Lo que pierde es flexibilidad y eficiencia de preparación: si el plato se preparó con un ingrediente equivocado, o si mañana se necesita una versión distinta del mismo plato, hay que rehacer el plato completo desde la cocina, no solo cambiar un frasco en la despensa. La OBT (mart_daily_sales_obt) es exactamente esa mesa servida: rápida de consultar, pero cara de mantener cuando algo que ya está "servido" en cada fila necesita cambiar.
Resumen y siguiente paso
Este módulo toma el star schema que dejó verificado el módulo 2 y lo somete a dos comparaciones reales: normalizarlo más (snowflake, con dim_category) y denormalizarlo por completo (OBT, con mart_daily_sales_obt). Vas a medir, con EXPLAIN y con conteos literales, el costo real de cada dirección —no vas a suponerlo—, y vas a cerrar con criterio propio sobre cuándo cada una de las tres formas gana.
Antes de avanzar deberías poder: nombrar las tres formas que este módulo compara y qué construye cada una; explicar, con la analogía de esta lección, la diferencia entre normalizar y denormalizar; y decir de memoria cuál es la fila exacta del checklist del módulo 1 que este módulo resuelve.
La lección 2 empieza por normalizar: saca category de dim_product y construye, por primera vez en esta guía, un snowflake schema real, verificado.
Recursos
- Kimball Group — "Star Schema / OLAP Cube" — la fuente que define el vocabulario de snowflake schema que este módulo construye por primera vez. kimballgroup.com/data-warehouse-business-intelligence-resources/kimball-techniques/dimensional-modeling-techniques/star-schema-olap-cube. En inglés.
- Fivetran — "Star Schema vs. OBT for Data Warehouse Performance" — el benchmark real (Redshift, Snowflake, BigQuery) que sostiene el argumento cuantitativo de este módulo: 25-50% más rápido con OBT, a costa de 2-3x más almacenamiento. fivetran.com/blog/star-schema-vs-obt. En inglés.
- dataarchitect.studio — "One Big Table vs the Star Schema: The Real Trade-off" — el argumento cualitativo que complementa el benchmark de Fivetran: ninguna forma gana universalmente. dataarchitect.studio/essays/one-big-table-vs-star-schema. En inglés.
- DuckDB — "EXPLAIN: Inspect Query Plans" — la guía oficial del comando que la lección 3 usa para comparar, no para afinar, el costo de un
JOIN. duckdb.org/docs/current/guides/meta/explain. En inglés.