Un product manager pide los ingresos por categoría y región del último trimestre, frente al mismo trimestre de un año antes. En la base de datos de pedidos eso significa unir cinco tablas, construir un calendario al vuelo y recordar que los clientes se mudan entre regiones. Cada pregunta nueva necesita joins nuevos, y dos analistas obtienen dos cifras.
Un esquema en estrella lo resuelve remodelando los datos en torno a un proceso de negocio: una tabla de hechos con mediciones en el centro y, a su alrededor, tablas de dimensiones que las describen. Este artículo diseña un esquema en estrella de ventas paso a paso, de la granularidad al SQL, incluyendo dimensiones de cambio lento, estrella frente a copo de nieve y almacenes columnares.
Qué es un esquema en estrella
Un esquema en estrella tiene dos tipos de tablas:
- Las tablas de hechos registran mediciones de un evento de negocio, como una línea de pedido: claves foráneas a las dimensiones más medidas numéricas. Son estrechas y largas.
- Las tablas de dimensiones contienen el contexto, el quién, qué, dónde y cuándo: tablas anchas, planas y cortas de atributos de texto como categoría o región.
El Kimball Group, que dio nombre a muchas de estas técnicas, lo describe como tablas de hechos enlazadas a tablas de dimensiones mediante claves primarias y foráneas. La guía de Power BI de Microsoft resume su atractivo con sencillez: las tablas de dimensiones permiten filtrar y agrupar, las tablas de hechos permiten resumir.
Diséñalo en cuatro pasos
El proceso de diseño dimensional en cuatro pasos de Kimball son cuatro decisiones, en este orden:
- Elegir el proceso de negocio: ventas minoristas, en tiendas y en una tienda web.
- Declarar la granularidad: una fila por línea de pedido, es decir, un producto en un pedido. Kimball llama a la granularidad un contrato vinculante, declarado antes que cualquier dimensión o hecho.
- Identificar las dimensiones: fecha, producto, tienda, cliente y promoción, cada una con un valor por línea de pedido.
- Identificar los hechos: cantidad, precio unitario, descuento, importe neto y coste, todos ciertos con esa granularidad.
Empieza por la granularidad atómica, el nivel más bajo que captura la fuente; los resúmenes salen de filas atómicas, nunca al revés. Nunca mezcles granularidades: un gasto de envío cobrado por pedido pero almacenado en cada línea se cuenta una vez por línea. Repártelo entre las líneas con una regla de negocio, como aconseja Kimball, o dale su propia tabla de hechos.
Las tablas del ejemplo
| Columna de fact_sales | Rol | Notas |
|---|---|---|
date_key |
Clave foránea | Clave inteligente como 20261120 |
product_key, store_key, customer_key, promotion_key |
Claves foráneas | Enteros sustitutos |
order_number |
Dimensión degenerada | Sin tabla de dimensión detrás |
line_number |
Identidad de la fila | Único junto con order_number |
quantity, discount_amount, net_amount, cost_amount |
Medidas aditivas | Se suman por cualquier dimensión |
unit_price |
Medida no aditiva | Nunca la sumes |
Las dimensiones son planas a propósito: dim_product lleva marca, subcategoría y categoría en una fila, y dim_store trata la tienda web como una tienda más con channel = 'online'. order_number es una dimensión degenerada: el contexto de la cabecera vive en otras dimensiones, solo queda el número, que sigue siendo útil para agrupar cestas.
El precio unitario es la trampa clásica: sumado entre líneas no significa nada, así que divide partes aditivas, como en SUM(net_amount) / SUM(quantity).
Claves sustitutas y la dimensión de fecha
Cada dimensión recibe una clave sustituta, un entero sin significado asignado en secuencia desde 1, porque una dimensión que guarda historial tiene varias filas por clave natural y las fuentes suelen discrepar en las claves. Conserva la clave natural (sku, store_code, customer_id) como atributo para rastrear las filas hasta la fuente.
La dimensión de fecha es la excepción: estable y predecible, así que su clave puede tener significado, como 20261120, lo que además ayuda a particionar. Filtra y agrupa por sus atributos (year, quarter, is_holiday), no con aritmética sobre la clave. Veinte años son solo unas 7 305 filas (20 × 365,25).
Las claves foráneas de los hechos nunca deberían ser nulas: una clave nula elimina la fila de los inner joins y los totales se reducen en silencio. Usa filas de dimensión por defecto para valores desconocidos o no aplicables; aquí, promotion_key 0 significa «Sin promoción».
Dimensiones de cambio lento
El tipo de dimensión de cambio lento (SCD) decide qué muestran los informes después de que cambie un atributo:
| Tipo | Nombre | Ante un cambio | Historial conservado |
|---|---|---|---|
| 0 | Conservar el original | El valor nunca cambia | Solo el valor original |
| 1 | Sobrescribir | Actualizar la fila en su sitio | Ninguno |
| 2 | Añadir fila nueva | Cerrar la fila actual, insertar una nueva con clave nueva | Completo |
| 3 | Añadir atributo nuevo | Copiar el valor antiguo a una columna prior_, sobrescribir |
Un valor anterior |
Los tipos 4 a 7 de Kimball añaden minidimensiones e híbridos de estos.
El tipo 2 es el caballo de batalla. El cliente C-1042 se muda de Austin a Denver el 20 de noviembre de 2026:
| customer_key | customer_id | city | valid_from | valid_to | is_current |
|---|---|---|---|---|---|
| 1001 | C-1042 | Austin | 2024-03-14 | 2026-11-20 | false |
| 2417 | C-1042 | Denver | 2026-11-20 | 9999-12-31 | true |
Las ventas anteriores conservan la clave 1001 y siguen en Austin; las posteriores reciben 2417. La carga de hechos elige la fila cuyo rango contiene la fecha del pedido (valid_from <= order_date < valid_to). La definición del tipo 2 de Kimball pide al menos una fecha de vigencia, una fecha de caducidad y un indicador de fila actual; un valid_to en un futuro lejano en lugar de nulo mantiene simples los filtros de rango. Los snapshots de dbt implementan el tipo 2 por ti.
Para informar de todo el historial según la ciudad actual, haz join con la fila actual del mismo customer_id. La captura de datos de cambios alimenta bien el tipo 2, porque cada actualización llega como un evento; consulta el diagrama de change data capture.
Esquema en estrella vs esquema copo de nieve vs una sola tabla grande
| Esquema en estrella | Esquema copo de nieve | Una sola tabla grande | |
|---|---|---|---|
| Datos de dimensión | Una tabla plana por dimensión | Cadenas normalizadas: producto, subcategoría, categoría | Copiados en cada fila de hechos |
| Joins por consulta | Uno por dimensión usada | Uno por tabla de cada cadena | Ninguno |
| Corregir un atributo | Actualizar una tabla de dimensión | Actualizar una tabla de la cadena | Reescribir cada fila de hechos afectada |
| Ideal para | La mayoría de los data marts y modelos de BI | Rara vez compensa para analítica | Un panel o una extracción de ML, generados desde una estrella |
Kimball desaconseja las dimensiones en copo de nieve: son más difíciles de recorrer para los usuarios de negocio, pueden perjudicar el rendimiento de las consultas, y una dimensión aplanada contiene exactamente la misma información. La guía de Power BI coincide.
Una sola tabla grande no necesita joins, y la compresión columnar mantiene baratos los valores repetidos. El coste es el mantenimiento: las correcciones se vuelven reescrituras y los atributos nuevos reconstrucciones, así que genérala a partir de la estrella.
Una consulta que el modelo facilita
SELECT d.year_month,
p.category,
SUM(f.net_amount) AS revenue,
SUM(f.net_amount - f.cost_amount) AS gross_margin,
SUM(f.net_amount) / NULLIF(SUM(f.quantity), 0) AS avg_selling_price
FROM fact_sales AS f
JOIN dim_date AS d ON d.date_key = f.date_key
JOIN dim_product AS p ON p.product_key = f.product_key
JOIN dim_store AS s ON s.store_key = f.store_key
WHERE d.year = 2026
AND s.channel = 'online'
GROUP BY d.year_month, p.category
ORDER BY d.year_month, revenue DESC;
Cada join va del hecho a una dimensión por una clave entera; los filtros están en atributos de dimensión y las sumas en columnas de hechos. Una pregunta nueva cambia el WHERE y el GROUP BY, no los joins, y por eso las herramientas de BI generan estas consultas con fiabilidad.
Rendimiento en almacenes columnares
Los almacenes columnares leen solo las columnas que usa una consulta, lo que encaja con una tabla de hechos estrecha, y las dimensiones pequeñas se unen a bajo coste: en un broadcast join, BigQuery envía la tabla más pequeña a cada worker. Con claves INT64 y cantidades de 8 bytes lógicos e importes NUMERIC de 16, la consulta anterior lee 64 bytes por línea de pedido del hecho, unos 32 GB para 500 millones de líneas, por ancha que sea la tabla.
- Poda por fecha. Particiona, agrupa o ordena el hecho por
date_key. BigQuery poda particiones cuando la propia columna de partición tiene un filtro constante, así que los escaneos grandes también deberían filtrardate_keycon constantes, no solodim_date.year. - Declara las claves, aunque no se apliquen. BigQuery usa claves primarias y foráneas declaradas
NOT ENFORCEDpara eliminar y reordenar joins, y Snowflake elimina joins para las restricciones marcadasRELY. Ninguno comprueba los datos, y las restricciones violadas pueden dar resultados erróneos, así que prueba las claves en el pipeline. - Colocaliza en Redshift. La guía de Redshift recomienda distribuir el hecho y una dimensión por su clave común, usar distribución ALL en otras dimensiones, o dejar que
AUTOdecida.
Los esquemas en estrella suelen vivir en la capa gold de una arquitectura medallion; el diagrama de arquitectura de pipeline de datos muestra cómo llegan los datos allí, y ETL vs ELT dónde se ejecutan las transformaciones.
El diagrama del esquema en estrella de ventas
Contenido del diagrama
- fact_sales
- dim_date
- dim_customer
- dim_product
- dim_promotion
- dim_store
- date_key
- customer_key
- promotion_key
- product_key
- store_key
Cómo leerlo:
- fact_sales en el centro contiene cinco claves foráneas, el
order_numberdegenerado,line_numbery cinco medidas. - Patas de gallo en cada línea: cero o muchas filas de hechos en un extremo, exactamente una fila de dimensión en el otro. Las etiquetas nombran las claves de join.
- Las tarjetas turquesa son dimensiones, con dim_date arriba y el resto a izquierda y derecha.
- La tarjeta ámbar, dim_customer, guarda el historial como SCD tipo 2.
- El bloque Markdown indica la granularidad y qué medidas puedes sumar.
Cómo dibujar con claridad diagramas en estrella
- Escribe la granularidad en el diagrama. Los revisores la piden primero, y una granularidad mal leída causa dobles conteos.
- Pon el hecho en el centro y cada dimensión a un salto. Una línea entre dos dimensiones es un copo de nieve; dibújala a propósito o aplana.
- Marca los roles de las claves: PK, FK, NK para claves naturales y DD para dimensiones degeneradas.
- Muestra el tipo de SCD en las dimensiones que guardan historial.
- Deja fuera los jobs de carga, las tablas de staging y las columnas por las que nadie filtra.
- Dibuja un proceso de negocio por diagrama. Cuando varias tablas de hechos comparten dimensiones conformadas, añade una matriz de bus: procesos como filas, dimensiones como columnas.
Consulta el ejemplo de diagrama ER para la notación de pata de gallo.
Constrúyelo en Archboard
- Inicia sesión con Google o GitHub y crea un diagrama.
- Si las tablas existen, pega sus sentencias
CREATE TABLEen la herramienta de SQL a diagrama ER, o el DBML en la herramienta de DBML a diagrama ER, para obtener un primer borrador. - Añade una Entidad del paquete Entidad-relación llamada fact_sales, un atributo por línea: cinco claves marcadas FK, order_number marcado DD, line_number y después las medidas. Dale un acento azul.
- Añade cinco nodos Entidad más: dim_date encima del hecho, dim_customer y dim_promotion a la izquierda, dim_product y dim_store a la derecha. Marca las claves sustitutas como PK y las naturales como NK; usa acentos turquesa, ámbar para dim_customer.
- Conecta fact_sales con cada dimensión con patas de gallo, cero o muchas en el hecho y exactamente una en la dimensión, y etiqueta cada línea con su clave.
- Añade notas Texto del paquete Básico: una leyenda de claves, la regla del SCD tipo 2, la clave de fecha inteligente, la fila de promoción por defecto y la tienda web.
- Añade un bloque Markdown del paquete Básico debajo del hecho con la granularidad y las reglas de aditividad.
El ejemplo usa 7 nodos de diagrama, dentro del plan Free.
Preguntas frecuentes
¿Qué es un esquema en estrella en términos sencillos? Una tabla de hechos central con mediciones, como importes de ventas, enlazada a tablas de dimensiones que las describen, como fecha, producto y cliente. Dibujado, parece una estrella.
¿Cuál es la diferencia entre una tabla de hechos y una tabla de dimensiones? Una tabla de hechos almacena mediciones de eventos con una granularidad declarada, más claves foráneas. Una tabla de dimensiones almacena los atributos por los que filtras y agrupas, como categoría o región.
¿Cuál es la diferencia entre un esquema en estrella y un esquema copo de nieve? Una estrella guarda cada dimensión en una tabla plana; un copo de nieve la normaliza en cadenas como producto, subcategoría y categoría. Las estrellas necesitan menos joins y son más fáciles de usar.
¿Está normalizado un esquema en estrella? No del todo. Las tablas de dimensiones están desnormalizadas a propósito, y repiten valores como los nombres de categoría en cada fila de producto, mientras que la tabla de hechos se mantiene estrecha.
¿Siguen importando los esquemas en estrella en los almacenes columnares? Sí. Las tablas anchas se escanean a bajo coste en los motores columnares, pero una estrella te sigue dando un solo lugar donde corregir un atributo, historial mediante dimensiones de cambio lento y respuestas coherentes entre informes.