erDiagram
HECHO_EVALUACION_ACADEMICA {
int id_estudiante
int id_asignatura
int id_tiempo
decimal calificacion
int creditos
int indicador_reprobado
}
DIMENSION_ESTUDIANTE {
int id_estudiante
varchar nombre
varchar sexo
varchar generacion
varchar carrera
varchar facultad
}
DIMENSION_ASIGNATURA {
int id_asignatura
varchar nombre_asignatura
varchar tipo_asignatura
}
DIMENSION_TIEMPO {
int id_tiempo
varchar semestre
int anio
varchar periodo
}
HECHO_EVALUACION_ACADEMICA }o--|| DIMENSION_ESTUDIANTE : pertenece_a
HECHO_EVALUACION_ACADEMICA }o--|| DIMENSION_ASIGNATURA : corresponde_a
HECHO_EVALUACION_ACADEMICA }o--|| DIMENSION_TIEMPO : ocurre_en
8 Esquemas Dimensionales y Redundancia
En el capítulo anterior se definieron hechos, dimensiones, granularidad y aditividad. Ahora corresponde decidir cómo se estructuran físicamente las dimensiones alrededor del hecho: qué tan normalizadas están, cuánta redundancia se acepta y qué impacto real tiene esa decisión sobre el costo y la velocidad de consulta.
8.1 Esquema de Estrella
El esquema estrella consiste en una tabla de hechos central conectada directamente a varias dimensiones. Sus dimensiones suelen estar desnormalizadas (redundancia controlada) para reducir joins y optimizar consultas analíticas.
En el ejemplo académico, la dimensión estudiante incluye atributos como carrera y facultad en la misma tabla, lo que simplifica consultas típicas del estilo “promedio por carrera y por semestre”. El costo es aceptar repetición (por ejemplo, repetir “ciencias” como facultad en muchos registros).
Desde el punto de vista estructural, el modelo puede representarse así:
En este esquema:
- La tabla de hechos está al centro.
- Todas las dimensiones se conectan directamente a ella.
- No existen relaciones entre dimensiones.
- La información jerárquica (por ejemplo, carrera → facultad) se almacena dentro de la misma dimensión.
8.2 Esquema de Copo de Nieve (Snowflake)
El esquema de copo de nieve normaliza las dimensiones en subdimensiones. Por ejemplo, separar “carrera” y “facultad” en tablas propias relacionadas jerárquicamente, evitando redundancia, pero aumentando joins y complejidad de consulta.
erDiagram
HECHO_EVALUACION_ACADEMICA {
int id_estudiante
int id_asignatura
int id_tiempo
decimal calificacion
int creditos
int indicador_reprobado
}
DIMENSION_ESTUDIANTE {
int id_estudiante
varchar nombre
varchar sexo
varchar generacion
int id_carrera
}
DIMENSION_CARRERA {
int id_carrera
varchar nombre_carrera
int id_facultad
}
DIMENSION_FACULTAD {
int id_facultad
varchar nombre_facultad
}
DIMENSION_ASIGNATURA {
int id_asignatura
varchar nombre_asignatura
varchar tipo_asignatura
}
DIMENSION_TIEMPO {
int id_tiempo
varchar semestre
int anio
varchar periodo
}
HECHO_EVALUACION_ACADEMICA }o--|| DIMENSION_ESTUDIANTE : pertenece_a
HECHO_EVALUACION_ACADEMICA }o--|| DIMENSION_ASIGNATURA : corresponde_a
HECHO_EVALUACION_ACADEMICA }o--|| DIMENSION_TIEMPO : ocurre_en
DIMENSION_ESTUDIANTE }o--|| DIMENSION_CARRERA : estudia
DIMENSION_CARRERA }o--|| DIMENSION_FACULTAD : pertenece
En este esquema:
- La dimensión estudiante ya no almacena directamente la facultad.
- Carrera y facultad se modelan como entidades independientes.
- Se elimina redundancia.
- Se incrementa el número de joins necesarios para consultas que involucren facultad.
8.2.1 Comparación conceptual
| Aspecto | Estrella | Copo de nieve |
|---|---|---|
| Normalización | Baja (desnormalizada) | Alta |
| Redundancia | Controlada | Reducida |
| Número de joins | Menor | Mayor |
| Complejidad conceptual | Más simple | Más estructurada |
| Rendimiento OLAP | Generalmente mejor | Puede disminuir |
| Gobierno de datos | Más flexible | Más controlado |
8.2.2 Lo mismo, en SQL
Con un dominio distinto (ventas), la diferencia entre ambos esquemas se ve directamente en la consulta:
-- Estrella: un solo join por dimensión
select
d.anio,
p.categoria,
sum(f.importe) as ventas
from fact_ventas as f
join dim_fecha as d using (fecha_key)
join dim_producto as p using (producto_key)
group by d.anio, p.categoria;-- Copo de nieve: dim_producto no tiene "categoria", hay que subir un nivel más
select
d.anio,
c.categoria,
sum(f.importe) as ventas
from fact_ventas as f
join dim_fecha as d using (fecha_key)
join dim_producto as p using (producto_key)
join dim_categoria as c using (categoria_key)
group by d.anio, c.categoria;| Criterio | Estrella | Copo de nieve |
|---|---|---|
| Consultas | Simples, pocas uniones | Más uniones |
| Redundancia | Mayor | Menor |
| Mantenimiento | Directo | Más entidades y claves |
| Uso recomendado | BI y autoservicio | Jerarquías grandes o compartidas |
8.3 Redundancia controlada: qué cuesta realmente desnormalizar
La comparación anterior suele quedarse en “el copo de nieve reduce redundancia”, como si repetir datos fuera automáticamente malo. En un almacén de datos columnar (Parquet, Delta, BigQuery, Snowflake) esa redundancia casi no cuesta lo que parece.
Un motor columnar no almacena cada valor de texto tal cual: aplica codificación por diccionario (cada valor distinto se guarda una sola vez y cada fila solo referencia un código corto) y run-length encoding (RLE) sobre columnas ordenadas o de baja cardinalidad. Si dim_producto tiene 500,000 filas y la columna categoria solo tiene 20 valores distintos, el motor no almacena “Electrónica” 25,000 veces: almacena el diccionario de 20 categorías una vez y, por fila, un índice de unos cuantos bits.
En la práctica esto significa que:
- Guardar
categoriadirectamente endim_producto(estrella) cuesta, en almacenamiento, un orden de magnitud similar a guardarcategoria_key(copo de nieve) — ambos terminan siendo, a nivel de disco, un entero pequeño por fila. - El ahorro de espacio que promete la normalización es, en un motor columnar moderno, marginal frente a lo que cuesta en 1990 con motores orientados a fila.
- Lo que sí cambia entre ambos esquemas no es tanto el almacenamiento, sino el costo de consulta.
8.4 Costo de consulta: joins y motores en la nube
El argumento a favor de la estrella no es “ocupa menos espacio”, es “cada consulta hace menos trabajo”. Esto importa especialmente cuando las dimensiones no son pequeñas.
Considera una dimensión cliente con 5 millones de filas, normalizada como dim_cliente → dim_ciudad → dim_estado (jerarquía geográfica en copo de nieve) contra la misma dimensión desnormalizada donde estado vive directamente en dim_cliente.
Para responder “ventas totales por estado”:
- Estrella:
fact_ventas(100M filas) hace un solo join contradim_cliente(5M filas), que ya traeestado. - Copo de nieve:
fact_ventashace join contradim_cliente(5M filas), y ese resultado intermedio vuelve a hacer join contradim_ciudad(5K filas) y luego contradim_estado(32 filas).
El segundo camino no es más caro por las tablas pequeñas (dim_ciudad, dim_estado) — es más caro porque agrega etapas de shuffle sobre un resultado intermedio ya grande (la salida del primer join, del tamaño de fact_ventas). Cada hop adicional en la cadena de joins vuelve a mover ese volumen de datos por la red del clúster.
Esto se traduce directamente en costo facturable:
- BigQuery cobra por bytes escaneados/procesados; más joins sobre resultados intermedios grandes significan más bytes movidos y más slots ocupados.
- Snowflake cobra por tiempo de cómputo del warehouse (créditos); una cadena de joins más larga generalmente implica más segundos de warehouse activo para la misma pregunta.
Regla práctica: la redundancia de un esquema estrella es barata cuando vive en una dimensión (se paga una vez, al cargar). El costo de un join extra se paga en cada consulta, multiplicado por cuántas veces se ejecuta esa consulta al día. Por eso los data marts orientados a BI casi siempre prefieren estrella, y el copo de nieve se reserva para jerarquías muy grandes, compartidas entre muchos data marts, donde mantener una sola versión de la verdad importa más que el costo por consulta.
8.5 Esquema de Constelación (Fact Constellation / Galaxy Schema)
Cuando dos o más procesos de negocio comparten las mismas dimensiones, tiene sentido que compartan también las tablas físicas: eso es un esquema de constelación. En vez de una estrella aislada por proceso, varias tablas de hechos se conectan a un conjunto de dimensiones conformadas (misma clave, mismo grano, mismo significado en todos los hechos que la usan).
Retomando el dominio hospitalario: además de FACT_CANCELACION_CITA (una fila por cita programada), el hospital también factura por cada servicio prestado. En vez de crear dimensiones nuevas y redundantes para ese segundo proceso, se reutilizan DIM_FECHA, DIM_HOSPITAL, DIM_SERVICIO, DIM_PACIENTE y DIM_ASEGURADORA:
erDiagram
FACT_CANCELACION_CITA {
int appointment_key
int fecha_key
int hospital_key
int servicio_key
int paciente_key
int aseguradora_key
int cancelled_flag
int no_show_flag
}
FACT_FACTURACION_HOSPITALARIA {
int factura_key
int fecha_key
int hospital_key
int servicio_key
int paciente_key
int aseguradora_key
decimal monto_facturado
decimal monto_cubierto
decimal monto_paciente
}
DIM_FECHA {
int fecha_key
date fecha
int mes
int anio
}
DIM_HOSPITAL {
int hospital_key
string hospital_nombre
string estado
}
DIM_SERVICIO {
int servicio_key
string servicio_nombre
string especialidad
}
DIM_PACIENTE {
int paciente_key
int edad
string genero
}
DIM_ASEGURADORA {
int aseguradora_key
string aseguradora_nombre
string tipo
}
FACT_CANCELACION_CITA }o--|| DIM_FECHA : fecha
FACT_CANCELACION_CITA }o--|| DIM_HOSPITAL : hospital
FACT_CANCELACION_CITA }o--|| DIM_SERVICIO : servicio
FACT_CANCELACION_CITA }o--|| DIM_PACIENTE : paciente
FACT_CANCELACION_CITA }o--|| DIM_ASEGURADORA : aseguradora
FACT_FACTURACION_HOSPITALARIA }o--|| DIM_FECHA : fecha
FACT_FACTURACION_HOSPITALARIA }o--|| DIM_HOSPITAL : hospital
FACT_FACTURACION_HOSPITALARIA }o--|| DIM_SERVICIO : servicio
FACT_FACTURACION_HOSPITALARIA }o--|| DIM_PACIENTE : paciente
FACT_FACTURACION_HOSPITALARIA }o--|| DIM_ASEGURADORA : aseguradora
Esto resuelve exactamente el tipo de redundancia más caro: sin dimensiones conformadas, cada equipo que construye un nuevo data mart terminaría creando su propia versión de “paciente” o “servicio”, con distinta granularidad, distintos códigos y sin forma de cruzar ambos hechos (“¿cuántas citas canceladas generaron facturación parcial de cualquier forma?”). Con dimensiones conformadas, ambos hechos son comparables y combinables por las mismas llaves.
8.6 Patrones de diseño para gestionar redundancia
Además de estrella, copo de nieve y constelación, hay patrones puntuales para decidir dónde vive cada dato — evitando tanto la redundancia descontrolada como los joins innecesarios.
Patrones para decidir dónde vive cada dato
La elección depende de qué se comparte, qué identifica al hecho, qué cambia y qué cardinalidad existe.
Dimensión conformada
Una dimensión común sirve a varios hechos con el mismo grano, llaves y reglas.
Reutilizar sin duplicarDimensión degenerada
El identificador permanece en el hecho porque no tiene atributos propios.
Evitar una tabla vacíaJunk dimension
Los indicadores de baja cardinalidad se agrupan como combinaciones en una dimensión.
Agrupar columnas dispersasRole-playing dimension
Una dimensión física asume varios roles; por ejemplo,
pedido, envío y pago sobre DIM_FECHA.
Mini-dimensión
Los atributos volátiles se separan para no versionar toda una dimensión grande.
Aislar cambios frecuentesTabla puente
Una relación muchos a muchos se representa sin duplicar filas de la tabla de hechos.
Preservar la cardinalidadEl diagrama presenta seis patrones de diseño que equilibran la redundancia y la complejidad de consulta.
Dimensiones conformadas. Una misma dimensión (mismo grano, mismas llaves) reutilizada por varios hechos, como DIM_FECHA en el ejemplo anterior. Es la base de cualquier esquema de constelación y evita que cada data mart reinvente “cliente” o “producto” con reglas distintas.
Dimensiones degeneradas. Un atributo que identifica al hecho (folio de orden, número de ticket) pero que no tiene atributos propios que ameriten una tabla de dimensión. Se guarda como columna directa en la tabla de hechos en vez de crear una dimensión de una sola columna.
Junk dimensions. Varios indicadores booleanos o de baja cardinalidad (es_promocion, metodo_pago, canal) se combinan en una sola dimensión “basurero” con una fila por combinación posible, en vez de agregar media docena de columnas sueltas al hecho o crear media docena de dimensiones diminutas.
Role-playing dimensions. La misma tabla física de dimensión se usa varias veces con distinto significado — por ejemplo, DIM_FECHA referenciada como fecha de pedido, fecha de envío y fecha de pago dentro del mismo hecho, mediante alias en la consulta (fecha_pedido_key, fecha_envio_key, fecha_pago_key, todas apuntando a la misma DIM_FECHA).
Mini-dimensiones. Cuando algunos atributos de una dimensión grande cambian con mucha frecuencia (ingresos estimados de un cliente, score de riesgo) separarlos en una dimensión pequeña propia evita que esos cambios disparen versiones nuevas de SCD tipo 2 en toda la fila del cliente — tema que se retoma en el siguiente capítulo.
Bridge tables. Cuando la relación entre el hecho y una dimensión es de muchos a muchos (un estudiante con varias becas activas, un diagnóstico con múltiples médicos tratantes) una tabla puente resuelve la relación sin duplicar filas del hecho ni forzar una cardinalidad que no existe en el negocio.
8.7 Conclusión
Estrella, copo de nieve y constelación no son alternativas puramente estéticas: cada una mueve el costo de un lugar a otro (almacenamiento vs. cómputo por consulta vs. complejidad de mantenimiento). En un motor columnar moderno, la redundancia dentro de una dimensión suele ser la opción más barata. Lo que todavía no se ha cubierto es qué pasa cuando esas dimensiones cambian — un cliente se muda de ciudad, un producto cambia de categoría. Ese es el tema del siguiente capítulo: dimensiones lentamente cambiantes (SCD).