Como criar uma Dimension Slowly Changing Type 4 em Modelação de Dados (Kimball)
Esta tarefa mostra como criar uma Dimension Slowly Changing Type 4 em Modelação de Dados (Kimball) — uma dimensão que mantém a versão actual e armazena o histórico completo numa tabela separada. É útil quando pretendes consultas rápidas sobre o estado actual e, ao mesmo tempo, conservar todo o historial para auditoria e análises temporais.
Pré-requisitos
- Conhecimentos básicos de SQL (SELECT, INSERT, UPDATE, JOIN).
- Um ambiente de base de dados para testes (por exemplo SQL Server, PostgreSQL ou Azure SQL).
- Dados de exemplo sobre entidades com alterações ao longo do tempo (ex.: clientes com morada e estado).
Passo 1: Entender o que é um SCD Tipo 4
Uma Slowly Changing Dimension (SCD) Tipo 4 separa o historial numa tabela de histórico e mantém apenas a versão actual na dimensão principal. A vantagem: consultas de leitura ao estado actual são rápidas e simples, enquanto o histórico fica disponível para análises detalhadas sem complicar a dimensão principal.
Passo 2: Definir o modelo de tabelas
Vamos criar duas tabelas: Dimension_Current (apenas registos actuais) e Dimension_History (todas as versões históricas). A Dimension_History terá uma chave surrogate, a chave natural da entidade, e campos de validade (valid_from, valid_to). A Dimension_Current tem a mesma chave natural e os atributos actuais.
-- Exemplo em SQL (compatível com SQL Server/Postgres com pequenas adaptações)
CREATE TABLE Dimension_Current (
customer_id INT PRIMARY KEY, -- chave natural
name VARCHAR(200),
address VARCHAR(300),
status VARCHAR(50),
last_updated TIMESTAMP
);
CREATE TABLE Dimension_History (
history_id SERIAL PRIMARY KEY, -- chave surrogate
customer_id INT, -- chave natural
name VARCHAR(200),
address VARCHAR(300),
status VARCHAR(50),
valid_from TIMESTAMP,
valid_to TIMESTAMP -- NULL significa ainda válido antes de mover para current
);
Passo 3: Carga inicial dos dados
Popula a Dimension_Current com o estado presente e a Dimension_History com um registo inicial que cobre desde o início até NULL (activo no presente).
-- Supõe que temos uma staging table Stg_Customers com o estado actual
INSERT INTO Dimension_Current (customer_id, name, address, status, last_updated)
SELECT customer_id, name, address, status, now()
FROM Stg_Customers;
INSERT INTO Dimension_History (customer_id, name, address, status, valid_from, valid_to)
SELECT customer_id, name, address, status, now(), NULL
FROM Stg_Customers;
Passo 4: Processo ETL para detectar mudanças e atualizar SCD Tipo 4
O fluxo ETL compara Stg_Customers com Dimension_Current. Para cada registo que mudou, fecha-se o período na Dimension_History (preenche-se valid_to) e insere-se um novo registo histórico com valid_from actual; actualiza-se a Dimension_Current com os novos atributos.
-- Exemplo transaccional simplificado
BEGIN;
-- 1) Fechar versões antigas na history para clientes que mudaram
UPDATE Dimension_History h
SET valid_to = now()
FROM Dimension_Current c
JOIN Stg_Customers s ON s.customer_id = c.customer_id
WHERE h.customer_id = c.customer_id
AND h.valid_to IS NULL
AND (c.name <> s.name OR c.address <> s.address OR c.status <> s.status);
-- 2) Inserir nova versão na history
INSERT INTO Dimension_History (customer_id, name, address, status, valid_from, valid_to)
SELECT s.customer_id, s.name, s.address, s.status, now(), NULL
FROM Stg_Customers s
JOIN Dimension_Current c ON s.customer_id = c.customer_id
WHERE (c.name <> s.name OR c.address <> s.address OR c.status <> s.status);
-- 3) Actualizar a dimensão actual
UPDATE Dimension_Current c
SET name = s.name,
address = s.address,
status = s.status,
last_updated = now()
FROM Stg_Customers s
WHERE c.customer_id = s.customer_id
AND (c.name <> s.name OR c.address <> s.address OR c.status <> s.status);
-- 4) Tratar novos clientes que não existem na Current
INSERT INTO Dimension_Current (customer_id, name, address, status, last_updated)
SELECT s.customer_id, s.name, s.address, s.status, now()
FROM Stg_Customers s
LEFT JOIN Dimension_Current c ON s.customer_id = c.customer_id
WHERE c.customer_id IS NULL;
INSERT INTO Dimension_History (customer_id, name, address, status, valid_from, valid_to)
SELECT s.customer_id, s.name, s.address, s.status, now(), NULL
FROM Stg_Customers s
LEFT JOIN Dimension_History h ON s.customer_id = h.customer_id AND h.valid_to IS NULL
WHERE h.history_id IS NULL;
COMMIT;
Passo 5: Consultas comuns (exemplos)
Exemplos úteis: obter o estado actual (simples) e reconstruir o estado numa data passada (usar Dimension_History).
-- Estado actual (simples)
SELECT * FROM Dimension_Current WHERE customer_id = 123;
-- Estado de um cliente numa data histórica
SELECT *
FROM Dimension_History
WHERE customer_id = 123
AND valid_from <= '2025-01-15'::timestamp
AND (valid_to IS NULL OR valid_to > '2025-01-15'::timestamp);
Verificar o resultado
Confirma que Dimension_Current reflecte os valores actuais e que Dimension_History tem entradas com valid_from/valid_to coerentes. Verifica casos de alteração: depois do ETL, o registo antigo na history deve ter valid_to preenchido e existir uma nova linha com valid_to NULL; a Current deve mostrar o novo valor.
Conclusão
A SCD Tipo 4 separa claramente estado actual e histórico, simplificando consultas e mantendo historial completo. Próximos passos: automatizar o processo ETL com jobs (ex.: Azure Data Factory ou SQL Agent), adicionar controlos de qualidade e gerir grandes volumes com partição em Dimension_History. Dica: começa com um subconjunto de clientes para testar e validar o comportamento antes de aplicar em produção.