Módulo 2: The Star Schema And Conformed Dimensions
Presentación del módulo: el star schema completo y las dimensiones conformadas
Por qué existe este módulo
El módulo 1 cerró con una sola pieza resuelta, y lo dijo explícitamente en su propio checklist de cierre: el grano de fact_orders quedó declarado y verificado —GRAIN_DECLARATION, con total_rows == distinct_lines, 40 == 40—, pero once piezas más seguían pendientes. La primera de esas piezas, la que este módulo resuelve de punta a punta, tiene tres partes: llaves sustitutas, dim_date y dimensiones conformadas.
Hoy, tal como quedó al cerrar el módulo 1, dim_store y dim_product siguen siendo exactamente lo que heredaste de foundations: catálogos con llave natural (store_id, product_id), sin ninguna columna que actúe como identificador propio del modelo dimensional. No existe dim_date en ningún lugar del proyecto —cualquier pregunta por día, semana o fin de semana tendría que derivarse de order_ts a mano, cada vez, sin una tabla reutilizable—. Y no existe, todavía, ningún vocabulario formal para hablar de "esta dimensión sirve a más de un proceso de negocio", porque hasta ahora solo ha existido un proceso de negocio: la venta.
Este módulo construye las tres piezas juntas, porque son, en realidad, una sola idea vista desde tres ángulos: un star schema completo es una tabla de hechos al centro, con dimensiones que la rodean, cada una unida por una sola llave —y esa llave, en un star schema bien construido, casi siempre es sustituta, no natural—. dim_date es la dimensión más universal de todas, la que casi cualquier proceso de negocio termina necesitando. Y una dimensión es conformada quando la misma tabla —con la misma llave, el mismo significado— sirve a más de un proceso, sin duplicarse. Al cerrar este módulo, Kiosko tiene, por primera vez, un star schema real: cuatro tablas, tres JOIN, y ni una fila de más ni de menos que las cuarenta que ya conoces.
Conexión con el módulo. Este módulo no toca el grano que declaraste en el módulo 1 —sigue siendo, palabra por palabra, "una línea de orden"—. Lo que construye es la estructura alrededor de ese grano: las llaves que identifican cada dimensión, la dimensión de calendario que faltaba, y el vocabulario para razonar sobre dimensiones compartidas antes de que este proyecto tenga un segundo proceso de negocio (el módulo 6 lo agrega, con fact_sessions).
Una analogía: el clóset con todo a la mano
Imagina que estás organizando un clóset. Hay dos formas razonables de hacerlo. La primera: cada prenda tiene su lugar fijo, visible, a un solo movimiento de la mano —las camisas en su barra, los pantalones en la suya, los zapatos en su repisa—. Buscar algo toma un paso: abres el clóset, ves la prenda, la tomas. La segunda forma: guardas las camisas dentro de una caja etiquetada "ropa de vestir", que a su vez está dentro de otra caja etiquetada "temporada fría", que a su vez está en un estante alto. Encontrar la misma camisa ahora toma varios pasos —bajar la caja grande, abrirla, encontrar la caja chica, abrirla—, aunque técnicamente la ropa esté mejor categorizada, con menos duplicación de espacio.
Un star schema es el primer clóset: la tabla de hechos al centro, y cada dimensión —dim_store, dim_product, dim_date— a un solo JOIN de distancia, sin capas intermedias. Un snowflake schema —que esta guía no construye todavía, pero que vas a ver de cerca en el módulo 3— es el segundo clóset: dimensiones normalizadas dentro de otras dimensiones (dim_product apuntando a dim_category, por ejemplo), con menos duplicación de datos pero con más pasos para llegar a cualquier pregunta. Este módulo construye el primer clóset —el star— porque es la forma que Kimball recomienda por defecto para un warehouse analítico, y porque entender bien un clóset ordenado es el requisito para apreciar, en el módulo 3, cuándo vale la pena pagar el costo de las cajas anidadas.
Ejemplo trabajado: el mapa del star completo, antes de construirlo
Antes de escribir la primera línea de SQL de este módulo, vale la pena ver, de un vistazo, las seis piezas que vas a construir y en qué lección aparece cada una.
# star_schema_map.py
COMPONENTS = [
("Anatomia de un star schema", "fact_orders al centro, dim_store/dim_product/dim_date alrededor, un JOIN de distancia"),
("Llaves sustitutas vs naturales", "store_key y product_key: enteros generados por el modelo, no por el sistema de origen"),
("dim_date", "La dimension de calendario que fact_orders nunca tuvo: date_key, day_of_week, quarter, is_weekend"),
("Dimensiones conformadas", "La MISMA dim_store y dim_date, listas para servir a mas de un proceso de negocio"),
("Bus matrix", "El mapa que dice que dimension sirve a que proceso, antes de construir nada nuevo"),
("El star ensamblado", "fact_orders + 3 JOIN, verificado: 40 filas antes, 40 despues -- ni una de mas ni de menos"),
]
print("=== El star schema completo de Kiosko, pieza por pieza (modulo 2) ===\n")
for i, (name, description) in enumerate(COMPONENTS, start=1):
print(f"{i}. {name}")
print(f" {description}\n")
print("Cada pieza depende de la anterior. Para el final del modulo, las cuatro tablas")
print("del star -- fact_orders, dim_store, dim_product, dim_date -- existen a la vez,")
print("unidas, sin perder ni duplicar ni una de las 40 filas del modulo 1.")
Qué esperar. Al correr python3 star_schema_map.py, la salida es exactamente esta:
=== El star schema completo de Kiosko, pieza por pieza (modulo 2) ===
1. Anatomia de un star schema
fact_orders al centro, dim_store/dim_product/dim_date alrededor, un JOIN de distancia
2. Llaves sustitutas vs naturales
store_key y product_key: enteros generados por el modelo, no por el sistema de origen
3. dim_date
La dimension de calendario que fact_orders nunca tuvo: date_key, day_of_week, quarter, is_weekend
4. Dimensiones conformadas
La MISMA dim_store y dim_date, listas para servir a mas de un proceso de negocio
5. Bus matrix
El mapa que dice que dimension sirve a que proceso, antes de construir nada nuevo
6. El star ensamblado
fact_orders + 3 JOIN, verificado: 40 filas antes, 40 despues -- ni una de mas ni de menos
Cada pieza depende de la anterior. Para el final del modulo, las cuatro tablas
del star -- fact_orders, dim_store, dim_product, dim_date -- existen a la vez,
unidas, sin perder ni duplicar ni una de las 40 filas del modulo 1.
Todavía ningún número real de Kiosko —este mapa es, deliberadamente, el plano antes de la construcción, igual que hizo el módulo 1 con el proceso de cuatro pasos de Kimball—. Pero fíjate en el orden: primero la forma (anatomía), después las llaves, después la dimensión que falta, después el vocabulario de reutilización, después el mapa de planeación, y solo al final el ensamblaje real. Ese orden no es casual: no tendría sentido construir dim_date antes de entender por qué una dimensión necesita una llave propia, ni hablar de dimensiones conformadas antes de que existan dimensiones bien formadas para conformar.
Diagrama: dónde estabas, dónde vas a estar
flowchart LR
subgraph M1["Modulo 1 (ya escrito)"]
A["fact_orders: grano declarado\n(una linea de orden, 40==40)\ndim_store/dim_product: llave natural"]
end
subgraph M2["Este modulo (2 de 8)"]
B["L2: Anatomia del star\n(fact al centro, dims alrededor)"]
C["L3: store_key, product_key\n(llaves sustitutas, EJECUTADO)"]
D["L4: dim_date\n(generate_date_dim, EJECUTADO)"]
E["L5-L6: Dimensiones conformadas\ny bus matrix"]
F["L7-L8: El star ensamblado\n3 JOIN, 40 -> 40, EJECUTADO"]
end
subgraph Resto["Modulos 3-8"]
G["Snowflake vs OBT, SCD,\njoin punto-en-el-tiempo..."]
end
A --> B --> C --> D --> E --> F --> G
El mapa de este módulo
Leccion Que construye
──────── ──────────────────────────────────────────────────────────────
L1 (esta) El mapa: las seis piezas del star, antes de construirlas
L2 Anatomia de un star schema completo: hecho al centro, dimensiones alrededor
L3 Llaves sustitutas vs naturales -- store_key, product_key, EJECUTADO
L4 Construyendo dim_date: generate_date_dim(), EJECUTADO en DuckDB
L5 Dimensiones conformadas: la misma tabla, mas de un proceso de negocio
L6 El bus matrix: el mapa de que dimension sirve a que proceso
L7 Ensamblando el primer star real de Kiosko -- los 3 JOIN, EJECUTADO
L8 Proyecto: el star schema completo de Kiosko en DuckDB
Las lecciones 2, 5 y 6 son mayormente conceptuales —construyen el vocabulario y el criterio antes de ejecutar código nuevo—, aunque cada una igual corre un ejemplo real sobre el fact_orders/dim_store/dim_product que ya conoces. Las lecciones 3, 4 y 7 son las que ejecutan la mayor parte del código nuevo de este módulo: las llaves sustitutas, dim_date, y el ensamblaje final con los tres JOIN. La lección 8 cierra con el mini-proyecto: el star schema completo, entregado y verificado contra los mismos números de revenue que ya conoces desde foundations.
Profundización: por qué el star schema es el siguiente paso lógico después del grano
Podría parecer que, una vez declarado el grano, el siguiente paso "obvio" sería empezar a resolver problemas más avanzados —historizar una dimensión que cambia, por ejemplo, o construir un accumulating snapshot—. Este módulo resiste esa tentación por una razón concreta: todo lo que sigue en esta guía asume que existe un star schema completo y bien formado debajo. El módulo 4 (SCD) historiza dim_product — pero solo tiene sentido historizar una dimensión que ya tiene una llave sustituta que pueda versionarse (lo vas a entender con precisión en la lección 3 de este módulo). El módulo 5 (join punto-en-el-tiempo) une fact_orders contra versiones históricas de una dimensión — pero ese join necesita, como punto de partida, el mismo patrón de JOIN limpio que este módulo construye en la lección 7. El módulo 6 (accumulating snapshot y cumulative design) construye fact_sessions y fact_store_activity — dos tablas de hechos nuevas que, desde el primer día, van a reutilizar dim_store y dim_date, precisamente porque este módulo las deja conformadas.
En otras palabras: el módulo 1 respondió "¿qué representa una fila?". Este módulo responde "¿con qué estructura vive esa fila, rodeada de qué contexto, y con qué llaves?". Sin esta respuesta, cualquier técnica más avanzada de los módulos 3 a 8 estaría construyéndose sobre una base que todavía no tiene forma.
Errores comunes
Pensar que "construir el star schema" significa solo dibujar el diagrama. Qué pasa: alguien dibuja, en un papel o en una herramienta de diagramación, un fact table al centro con líneas hacia tres dimensiones, y considera el trabajo de este módulo terminado. Por qué pasa: el diagrama es la parte visualmente más satisfactoria del star schema, y es fácil confundir "tengo el dibujo correcto" con "tengo las tablas correctas, con las llaves correctas, unidas sin perder filas". Cómo detectarlo: si al final de este módulo no puedes mostrar, con un JOIN ejecutado de verdad, que fact_orders unido a sus tres dimensiones sigue teniendo exactamente cuarenta filas, el diagrama fue un ejercicio de dibujo, no de modelado. Cómo corregirlo: cada lección de este módulo que promete un "Qué esperar" tiene que ejecutarse — el diagrama es un mapa útil, pero la evidencia es siempre la consulta corrida sobre DuckDB.
Saltarse dim_date porque "ya tengo order_ts en fact_orders". Qué pasa: alguien razona que, como fact_orders ya tiene una columna de fecha (order_ts), no hace falta ninguna tabla adicional — cualquier pregunta por fecha se puede resolver con funciones de fecha directamente sobre esa columna. Por qué pasa: order_ts técnicamente sí contiene la información cruda, y para preguntas simples (¿en qué mes fue esta orden?) parece suficiente. Cómo detectarlo: si tu única forma de responder "¿cuántas órdenes hubo en fin de semana?" es escribir una expresión de fecha distinta cada vez que alguien pregunta, en vez de un JOIN contra una tabla con is_weekend ya calculado, te falta dim_date — y ese cálculo repetido, disperso en veinte consultas distintas, es exactamente el tipo de trabajo duplicado que una dimensión conformada elimina. Cómo corregirlo: la lección 4 de este módulo construye dim_date precisamente para esto — calcular una vez, reutilizar siempre.
Creer que las llaves sustitutas son "trabajo extra sin beneficio inmediato" en este módulo. Qué pasa: alguien nota que, hoy, unir fact_orders a dim_store por store_id (llave natural) funciona perfectamente — no hay ningún cambio de tienda, ningún producto que se renombre, nada que justifique una llave adicional — y concluye que agregar store_key es puro formalismo. Por qué pasa: el beneficio real de una llave sustituta no se siente hasta que una dimensión empieza a cambiar (módulo 4) — hasta entonces, la llave natural "funciona igual". Cómo detectarlo: si tu razonamiento es "total, hoy da lo mismo", es el mismo error que ya viste en el módulo 1 con el grano — optimizar para el presente sin considerar el costo futuro. Cómo corregirlo: la lección 3 de este módulo explica, con la analogía del expediente de un cliente, por qué agregar la llave sustituta antes de necesitarla es la decisión correcta, no un adorno.
Ejercicios
Ejercicio 1 — Recuerda las tres piezas pendientes que resuelve este módulo, sin mirar atrás. Sin releer la sección "Por qué existe este módulo", escribe de memoria las tres piezas del checklist de cierre del módulo 1 que corresponden a este módulo 2 (revisa el proyecto de la lección 8 del módulo 1 si necesitas el nombre exacto de la fila del checklist).
Ver solución
La fila del checklist del módulo 1 que corresponde a este módulo dice, textualmente: "Llaves sustitutas, dim_date, dimensiones conformadas". Las tres piezas son: (1) reemplazar las llaves naturales de dim_store/dim_product por llaves sustitutas (store_key, product_key); (2) construir dim_date, la dimensión de calendario que foundations nunca necesitó; (3) formalizar el concepto de dimensión conformada — la misma tabla de dimensión, reutilizable por más de un proceso de negocio. Si tu respuesta mencionó estas tres piezas, aunque con otras palabras, tienes clara la meta de este módulo.
Ejercicio 2 — Ordena las seis piezas del mapa de esta lección, de memoria. Sin mirar el ejemplo trabajado, escribe en orden las seis piezas que este módulo construye (anatomía, llaves sustitutas, dim_date, dimensiones conformadas, bus matrix, el star ensamblado).
Ver solución
- Anatomía de un star schema
- Llaves sustitutas vs naturales
dim_date- Dimensiones conformadas
- Bus matrix
- El star ensamblado
Si invertiste el orden de "dimensiones conformadas" y "bus matrix" — pensando que el bus matrix viene primero, como mapa general —, no es un error grave: son conceptualmente muy cercanos. Pero fíjate en que esta guía enseña primero qué es una dimensión conformada (lección 5), con un ejemplo concreto (dim_store reutilizada), antes de generalizar esa idea en un mapa completo de todos los procesos de Kiosko (lección 6, el bus matrix). Se aprende el caso particular antes que el mapa general — el mismo patrón pedagógico que ya viste en el módulo 1 con el grano antes que la clasificación completa de hechos y dimensiones.
Ejercicio 3 — Explica la analogía del clóset con tus propias palabras. Usando la analogía del clóset ordenado (star) frente al clóset de cajas anidadas (snowflake) de esta lección, explica en 2-3 frases por qué esta guía elige construir primero el clóset con todo a la mano, aunque el módulo 3 muestre que las cajas anidadas también tienen sus ventajas.
Ver solución
El star schema (el clóset con todo a la mano) es la forma por defecto que recomienda Kimball para un warehouse analítico porque prioriza la simplicidad de consulta: cualquier pregunta de negocio necesita, como máximo, un JOIN para llegar del hecho a cualquier dimensión, sin pasos intermedios. El snowflake (las cajas anidadas) reduce la duplicación de datos —por ejemplo, no repetir el nombre de una categoría en cada fila de producto—, pero a cambio exige más JOIN para llegar a la misma información. Esta guía construye primero el star porque es la base que la mayoría de los casos de uso necesita, y porque entender bien esa base es lo que permite, en el módulo 3, decidir con criterio cuándo vale la pena pagar el costo adicional de normalizar una dimensión — no al revés.
Resumen y siguiente paso
Este módulo construye, sobre el grano ya declarado y verificado del módulo 1, el primer star schema completo de Kiosko: llaves sustitutas para dim_store y dim_product, la dimensión dim_date que faltaba, el vocabulario de dimensiones conformadas, el bus matrix como mapa de planeación, y el ensamblaje final de fact_orders con sus tres dimensiones, unidas sin perder ni duplicar ni una sola de las cuarenta filas que ya conoces.
Antes de avanzar deberías poder: nombrar las seis piezas de este módulo en orden; explicar, con tus propias palabras, la diferencia entre un star y un snowflake usando la analogía del clóset; 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 la anatomía: qué hace que una tabla sea un "hecho" y otra una "dimensión" en la forma física de un star schema, no solo en el vocabulario que ya aprendiste en el módulo 1.
Recursos
- Kimball Group — "Star Schema / OLAP Cube" — la fuente que define el vocabulario dimensional completo (dimensiones conformadas, llaves sustitutas, snowflake) que organiza este módulo. kimballgroup.com/data-warehouse-business-intelligence-resources/kimball-techniques/dimensional-modeling-techniques/star-schema-olap-cube. En inglés.
- Microsoft Learn — "Understand star schema and the importance for Power BI" — confirmación práctica y vendor-neutral del concepto de star schema, llaves sustitutas y dimensiones conformadas usado en este módulo. learn.microsoft.com/en-us/power-bi/guidance/star-schema. En inglés.
- "The Data Warehouse Toolkit", 3ra edición (Kimball & Ross, Wiley) — la referencia canónica de modelado dimensional que sostiene toda esta guía, ahora aplicada a la construcción completa del star. wiley.com/en-jp/The+Data+Warehouse+Toolkit. En inglés.