Como criar uma dimensão de calendário em SQL: passo a passo
Uma dimensão de calendário é a espinha dorsal de qualquer esquema em estrela: é ela que permite analisar métricas por ano, trimestre, mês ou dia da semana sem reescrever lógica de datas em cada relatório. Ao criá-la uma única vez e reutilizá-la, garante números consistentes em todo o data warehouse. Vamos criar uma dimensão de calendário em SQL, passo a passo, e ligá-la a uma tabela de factos.
Pré-requisitos
- Acesso a uma base de dados SQL Server (o exemplo adapta-se a PostgreSQL com pequenas alterações).
- Permissões para criar tabelas e inserir dados.
- Noções básicas de esquema em estrela: uma tabela de factos ligada a tabelas de dimensão.
Passo 1: Desenhar as colunas da dimensão
Antes de escrever SQL, decida que colunas a dimensão vai ter. A chave DateKey é um inteiro no formato AAAAMMDD (por exemplo, 20260705 para 5 de julho de 2026). Este tipo de chave — a que se chama smart key — é compacta, ordenável e legível, e torna as junções com a tabela de factos mais rápidas do que comparar valores de data. A seguir juntam-se os atributos que as pessoas usam para filtrar e agrupar: ano, trimestre, mês, nome do mês, dia e um indicador de fim de semana. A regra é simples: se o negócio filtra por esse atributo, ele merece uma coluna.
Passo 2: Criar a tabela DimDate
Crie a tabela com a DateKey como chave primária. Usar tipos pequenos, como tinyint e smallint, mantém a dimensão leve e rápida a consultar. Além disso, uma chave inteira permite reservar linhas especiais — por exemplo, -1 para uma data desconhecida — sem partir as junções.
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
);
Passo 3: Gerar as datas com uma CTE recursiva
Agora preenchemos um intervalo de datas — aqui, de 2015 a 2030 — com uma CTE recursiva. A primeira linha (o âncora) define a data inicial; o membro recursivo soma um dia de cada vez até chegar ao fim. Cada linha calcula os seus atributos a partir da data.
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);
Repare em dois pormenores. O CASE do fim de semana usa DATEDIFF(DAY, 0, d) % 7, uma fórmula independente do idioma do servidor (5 corresponde a sábado e 6 a domingo). E OPTION (MAXRECURSION 0) remove o limite de 100 iterações da CTE, permitindo gerar milhares de dias de uma só vez.
As colunasMonthNameeDayNameherdam o idioma da sessão de SQL Server. Se precisar de nomes sempre em português, defina a linguagem da sessão ou preencha esses textos manualmente.
Passo 4: Ligar à tabela de factos
A tabela de factos deve guardar a mesma DateKey inteira, e não a data completa. Assim a junção é rápida e a relação fica explícita através de uma chave estrangeira.
ALTER TABLE FactSales
ADD CONSTRAINT FK_FactSales_DimDate
FOREIGN KEY (DateKey) REFERENCES DimDate (DateKey);
Se for consumir o modelo no Power BI, importe a DimDate, crie a relação pela DateKey e use Marcar como Tabela de Datas, apontando para FullDate. Só assim as funções de Time Intelligence, como TOTALYTD, funcionam corretamente.
Verificar o resultado
Confirme que a dimensão ficou preenchida e experimente uma agregação por período:
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;
A primeira consulta deve devolver cerca de 5 844 dias (de 2015 a 2030). A segunda mostra as vendas por ano e trimestre — sinal de que a dimensão está bem ligada à tabela de factos.
Conclusão
Com poucas linhas de SQL passa a ter uma dimensão de calendário reutilizável em todo o warehouse. A partir daqui pode enriquecê-la com ano fiscal, número da semana ISO, feriados ou colunas de datas relativas, como "mês atual" ou "ano anterior". Qual é o atributo que mais falta faz nos seus relatórios — talvez uma coluna de feriados por país?