Como criar uma Slowly Changing Fact Table Tipo 3 em Modelação de Dados (Kimball)
Este tutorial explica como criar uma Slowly Changing Fact Table Tipo 3 em Modelação de Dados (Kimball) para preservar valores históricos limitados de atributos dentro da própria fact table. O padrão é útil quando precisa de comparar um valor actual com um valor anterior pontual (por exemplo, preço anterior) sem recorrer a uma estrutura SCD Tipo 2 completa que duplica linhas. Aqui mostramos porquê, quando usar, e um conjunto prático de passos e exemplos SQL para implementar e validar a solução.
Pré-requisitos
- Conhecimentos básicos de Modelação Dimensional Kimball e SQL.
- Ambiente com uma base de dados relacional (por exemplo SQL Server, PostgreSQL) para testes. Idealmente com dados de teste de 10k–1M linhas para validar desempenho.
- Exemplos de dados de transacções e dimensões (dim_customer, dim_product, dim_date) e uma noção da taxa de alteração (por exemplo, 2–10% dos produtos mudam de preço por mês).
- Ferramentas ETL/ELT (por exemplo, scripts SQL, Azure Data Factory, dbt) para automatizar actualizações.
Passo 1: Entender o padrão Slowly Changing Fact Table Tipo 3
Um SCD Tipo 3 na fact table guarda um atributo actual e um atributo anterior (ou N versões limitadas) dentro da própria fact. Em vez de criar linhas históricas (Tipo 2) que aumentam o volume de linhas e complicam agregações, o Tipo 3 mantém colunas como price_current e price_previous. Isto é adequado para análises de variação pontual (por exemplo, diferença de preço entre hoje e a última alteração) e para relatórios que não exigem um historial completo com todas as versões anteriores.
Passo 2: Escolher quais os atributos a versionar
Seleccione atributos que mudam ocasionalmente e em que apenas a última alteração anterior importa: preço, classificação de risco, estado de elegibilidade, promo_code activo. Exemplos concretos: se 95% das transacções precisam apenas de comparar preço actual vs anterior, manter price_current/price_previous reduz o custo de armazenamento. Evite o Tipo 3 para históricos longos ou requisitos de auditoria legais — nesses casos, o SCD Tipo 2 é mais apropriado.
Passo 3: Definir esquema da fact table
Crie a estrutura da fact com colunas para a chave da fact, chaves dimensionais, medidas e pares de colunas para versões (current/previous). Inclua carimbos temporais para saber quando ocorreu a mudança e metadados para rastreio. Indexe as colunas de chave e, se necessário, particione por date_key para queries analíticas.
CREATE TABLE fact_sales_scd3 (
sale_id BIGINT PRIMARY KEY,
customer_key INT REFERENCES dim_customer(customer_key),
product_key INT REFERENCES dim_product(product_key),
date_key INT,
quantity INT,
price_current NUMERIC(10,2),
price_previous NUMERIC(10,2) NULL,
price_changed_date TIMESTAMP NULL,
last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Considerar índices: CREATE INDEX idx_fact_sales_prod_date ON fact_sales_scd3(product_key, date_key);
Passo 4: Lógica ETL para inserir novas vendas
Para cada nova transacção insira price_current com o preço vigente e price_previous a NULL (ou igual a price_current se preferir convenção local). Em cenários de evento imutável (cada venda é única) a primeira inserção mantém previous NULL. Para loads em batch, processe em blocos (por exemplo 10k-100k rows) e use transacções para consistência.
INSERT INTO fact_sales_scd3 (
sale_id, customer_key, product_key, date_key, quantity, price_current
) VALUES (
1001, 200, 300, 20230901, 2, 19.99
);
Passo 5: Lógica ETL para actualizar preço existente (migrar current → previous)
Quando detectar que o preço do produto mudou e pretender actualizar fact rows que representam posições contínuas, actualize de forma transaccional: mover price_current para price_previous, colocar novo preço em price_current e registar a data de mudança. Para cenários com milhões de linhas afectadas, prefira operações por batches e use MERGE para reduzir contenção.
-- Exemplo com MERGE (PostgreSQL/SQL Server sintaxe conceptual)
BEGIN;
UPDATE fact_sales_scd3
SET price_previous = price_current,
price_current = 24.99,
price_changed_date = '2023-09-15',
last_updated = CURRENT_TIMESTAMP
WHERE product_key = 300
AND date_key <= 20230915
AND (price_current IS DISTINCT FROM 24.99);
COMMIT;
Nota: filtre para evitar actualizações desnecessárias e registe métricas (número de linhas afectadas). Para mudanças massivas, planifique janelas fora de horário de pico.
Passo 6: Tratar conflitos e dados históricos limitados
Defina a política quando price_previous já está preenchido: sobrescrever com a versão mais recente anterior (modo substituição), manter uma cadeia curta com colunas adicionais (price_prev2, price_prev3) ou rejeitar a alteração para fins de auditoria. Exemplo: manter até 2 versões anteriores reduz 99% dos requisitos de análise num cenário em que 85% das consultas só precisam de 1 anterior. Documente claramente a semântica para analistas e implemente testes que verifiquem o comportamento em casos de múltiplas mudanças em 30 dias.
Passo 7: Exemplo de consulta para análise de variação
Consultas típicas com uma fact Tipo 3 comparam price_current e price_previous para calcular diferença e percentagem. Trate os NULLs (primeira venda) utilizando COALESCE ou filtros.
SELECT
p.product_key,
p.product_name,
SUM(f.quantity) AS total_qty,
AVG(f.price_current) AS avg_price_current,
AVG(f.price_previous) AS avg_price_previous,
(AVG(f.price_current) - AVG(COALESCE(f.price_previous, f.price_current))) AS avg_price_diff,
CASE WHEN AVG(f.price_previous) IS NULL THEN NULL
ELSE (AVG(f.price_current) - AVG(f.price_previous)) / AVG(f.price_previous) * 100 END AS pct_change
FROM fact_sales_scd3 f
JOIN dim_product p ON f.product_key = p.product_key
GROUP BY p.product_key, p.product_name;
Verificar o resultado
Valide que price_current e price_previous estão correctos: compare amostras antes e depois, execute contagens (por exemplo, SELECT COUNT(*) WHERE price_previous IS NOT NULL) e valide que o número de linhas afectadas corresponde às expectativas (por ex., 50k linhas actualizadas). Crie testes automatizados que simulem primeira inserção, alteração única e alteração subsequente quando já existe um previous. Monitorize desempenho e registos de ETL para detectar regressões.
Conclusão
Uma Slowly Changing Fact Table Tipo 3 permite manter versões limitadas de atributos dentro da própria fact, simplificando análises de variação sem multiplicar linhas. Avalie sempre se o Tipo 3 satisfaz requisitos de auditoria ou se deve migrar para o Tipo 2. Próximos passos: automatizar a lógica ETL (agendamentos diários/horários), adicionar alertas quando o número de alterações exceder thresholds (por exemplo >5%/dia) e documentar a semântica de cada coluna current e previous para evitar interpretações erradas pelos analistas.