Cómo crear una dimensión de calendario en SQL: paso a paso
Una dimensión de calendario es la columna vertebral de cualquier esquema en estrella: permite analizar métricas por año, trimestre, mes o día de la semana sin reescribir la lógica de fechas en cada informe. Al crearla una sola vez y reutilizarla, mantienes números coherentes en todo el data warehouse. Vamos a crear una dimensión de calendario en SQL, paso a paso, y a enlazarla con una tabla de hechos.
Requisitos previos
- Acceso a una base de datos SQL Server (el ejemplo se adapta a PostgreSQL con pequeños cambios).
- Permisos para crear tablas e insertar datos.
- Nociones básicas de esquema en estrella: una tabla de hechos enlazada a tablas de dimensión.
Paso 1: Diseñar las columnas de la dimensión
Antes de escribir SQL, decide qué columnas tendrá la dimensión. La clave DateKey es un entero con formato AAAAMMDD (por ejemplo, 20260705 para el 5 de julio de 2026). Este tipo de clave —llamada smart key— es compacta, ordenable y legible, y hace que las uniones con la tabla de hechos sean más rápidas que comparar valores de fecha. A continuación, añade los atributos que la gente usa para filtrar y agrupar: año, trimestre, mes, nombre del mes, día y un indicador de fin de semana. La regla es sencilla: si el negocio filtra por un atributo, merece una columna.
Paso 2: Crear la tabla DimDate
Crea la tabla con DateKey como clave primaria. Usar tipos pequeños, como tinyint y smallint, mantiene la dimensión ligera y rápida de consultar. Además, una clave entera permite reservar filas especiales —por ejemplo, -1 para una fecha desconocida— sin romper las uniones.
CREATE TABLE DimDate (
DateKey int NOT NULL PRIMARY KEY,
FullDate date NOT NULL,
YearNumber smallint NOT NULL,
QuarterNumber tinyint NOT NULL,
MonthNumber tinyint NOT NULL,
MonthName varchar(20) NOT NULL,
DayNumber tinyint NOT NULL,
DayName varchar(20) NOT NULL,
IsWeekend bit NOT NULL
);
Paso 3: Generar las fechas con una CTE recursiva
Ahora rellenamos un intervalo de fechas —aquí, de 2015 a 2030— con una CTE recursiva. La primera fila (el ancla) define la fecha inicial; el miembro recursivo suma un día cada vez hasta llegar al final. Cada fila calcula sus atributos a partir de la fecha.
WITH Dates AS (
SELECT CAST('2015-01-01' AS date) AS d
UNION ALL
SELECT DATEADD(DAY, 1, d) FROM Dates WHERE d < '2030-12-31'
)
INSERT INTO DimDate (DateKey, FullDate, YearNumber, QuarterNumber, MonthNumber, MonthName, DayNumber, DayName, IsWeekend)
SELECT
CONVERT(int, FORMAT(d, 'yyyyMMdd')),
d,
YEAR(d),
DATEPART(QUARTER, d),
MONTH(d),
DATENAME(MONTH, d),
DAY(d),
DATENAME(WEEKDAY, d),
CASE WHEN (DATEDIFF(DAY, 0, d) % 7) IN (5, 6) THEN 1 ELSE 0 END
FROM Dates
OPTION (MAXRECURSION 0);
Fíjate en dos detalles. El CASE del fin de semana usa DATEDIFF(DAY, 0, d) % 7, una fórmula independiente del idioma del servidor (5 corresponde a sábado y 6 a domingo). Y OPTION (MAXRECURSION 0) elimina el límite de 100 iteraciones de la CTE, lo que permite generar miles de días de una sola vez.
Las columnasMonthNameyDayNameheredan el idioma de la sesión de SQL Server. Si necesitas los nombres siempre en un idioma concreto, define el idioma de la sesión o rellena esos textos manualmente.
Paso 4: Enlazar con la tabla de hechos
La tabla de hechos debe guardar la misma DateKey entera, no la fecha completa. Así la unión es rápida y la relación queda explícita mediante una clave foránea.
ALTER TABLE FactSales
ADD CONSTRAINT FK_FactSales_DimDate
FOREIGN KEY (DateKey) REFERENCES DimDate (DateKey);
Si vas a consumir el modelo en Power BI, importa DimDate, crea la relación por DateKey y usa Marcar como tabla de fechas, apuntando a FullDate. Solo así funcionan correctamente las funciones de Time Intelligence, como TOTALYTD.
Comprobar el resultado
Confirma que la dimensión se ha rellenado y prueba una agregación por periodo:
SELECT COUNT(*) AS TotalDays, MIN(FullDate) AS FirstDate, MAX(FullDate) AS LastDate
FROM DimDate;
SELECT d.YearNumber, d.QuarterNumber, SUM(f.Amount) AS Sales
FROM FactSales f
JOIN DimDate d ON f.DateKey = d.DateKey
GROUP BY d.YearNumber, d.QuarterNumber
ORDER BY d.YearNumber, d.QuarterNumber;
La primera consulta debería devolver unos 5.844 días (de 2015 a 2030). La segunda muestra las ventas por año y trimestre, señal de que la dimensión está bien enlazada con la tabla de hechos.
Conclusión
Con apenas unas líneas de SQL ya tienes una dimensión de calendario reutilizable en todo el warehouse. A partir de aquí puedes enriquecerla con año fiscal, número de semana ISO, festivos o columnas de fechas relativas, como "mes actual" o "año anterior". ¿Qué atributo echas más en falta en tus informes? ¿Quizás una columna de festivos por país?