Guia de Data Engineering

Guia de Modelagem Dimensional

A diferença entre um data warehouse que os analistas adoram e um que evitam? Modelagem dimensional. Aprenda a construir star schemas intuitivos e muito rápidos.

25 min de leituraPara analistas e data engineersExemplos SQL incluídos

1. O que é modelagem dimensional?

Modelagem dimensional é uma técnica para projetar data warehouses que coloca simplicidade de consulta e performance acima de eficiência de armazenamento. Criada por Ralph Kimball, segue como padrão porque modela os dados como o negócio pensa.

Por que não usar só 3NF?

Third Normal Form é ótima para OLTP: pouca redundância, sem anomalias de update. Para analytics é péssima: consultas exigem 15+ joins, performance cai e o modelo fica incompreensível para analistas.

Princípio central: separar o “o que aconteceu” do “contexto”

Tabelas fato

Guardam eventos e medidas: pedidos, cliques, pagamentos. Têm métricas agregáveis (SUM, COUNT, AVG).

Tabelas dimensão

Guardam contexto descritivo: quem (clientes), o quê (produtos), quando (datas), onde (locais). Permitem filtro e agrupamento.

Filosofia Kimball

"O data warehouse vale pelo BI que habilita." Modelos dimensionais são feitos para pessoas primeiro, máquinas depois. Se um analista não consegue escrever uma query sem ajuda, o modelo falhou.

2. Fatos vs dimensões

Os blocos fundamentais da modelagem dimensional. Acertando aqui, o resto acompanha.

Tabelas fato

  • Registram eventos/transações
  • Contêm medidas numéricas (valor, quantidade, duração)
  • Possuem chaves estrangeiras para dimensões
  • Altas e estreitas (muitas linhas, poucas colunas)
  • Grão = uma linha por evento

Exemplos:

fct_orders, fct_page_views, fct_payments

Tabelas dimensão

  • Guardam atributos descritivos
  • Possuem campos textuais para filtro/agrupamento
  • Têm surrogate key como chave primária
  • Baixas e largas (poucas linhas, muitas colunas)
  • Grão = uma linha por entidade

Exemplos:

dim_customers, dim_products, dim_date

PerguntaSe a resposta é...É uma...
Dá para fazer SUM/COUNT/AVG?SimFato
Você usaria em GROUP BY ou FILTER?SimDimensão
Descreve uma entidade?SimDimensão
Registra um evento/transação?SimFato

Exemplo: pedido e-commerce

-- Tabela fato: uma linha por item de pedido
CREATE TABLE fct_order_lines (
    order_line_sk       BIGINT PRIMARY KEY,    -- Surrogate key
    order_id            VARCHAR(50),           -- Natural key (dimensao degenerada)
    customer_sk         BIGINT REFERENCES dim_customers,
    product_sk          BIGINT REFERENCES dim_products,
    date_sk             INT REFERENCES dim_date,
    -- Medidas (agregáveis)
    quantity            INT,
    unit_price          DECIMAL(10,2),
    discount_amount     DECIMAL(10,2),
    line_total          DECIMAL(10,2)
);

-- Tabela dimensão: uma linha por cliente
CREATE TABLE dim_customers (
    customer_sk         BIGINT PRIMARY KEY,    -- Surrogate key
    customer_id         VARCHAR(50),           -- Natural key
    customer_name       VARCHAR(255),
    email               VARCHAR(255),
    segment             VARCHAR(50),           -- 'Enterprise', 'SMB', 'Consumer'
    acquisition_channel VARCHAR(50),
    created_at          TIMESTAMP
);

3. Design de star schema

Um star schema coloca a tabela fato no centro, cercada pelas dimensões. O nome vem do formato: fato no meio, dimensões irradiando como uma estrela.

Estrutura Star Schema

dim_date

dim_customer

fct_orders

dim_product

dim_store

Tabela fato no centro, dimensões ao redor — joins simples, consultas intuitivas

Star vs Snowflake Schema

Star schema (preferido)

  • • Dimensões desnormalizadas
  • • Menos joins = consultas mais rápidas
  • • Mais fácil de entender e consultar
  • • Alguma redundância (ok para analytics)

Snowflake schema

  • • Dimensões normalizadas
  • • Mais joins = consultas mais lentas
  • • Difícil de usar sem documentação
  • • Economiza armazenamento (raramente crítico)

Dica

Quando usar snowflake?

Use snowflake apenas quando a dimensão for realmente hierárquica E analistas consultarem níveis diferentes separadamente (produto → categoria → departamento). Caso contrário, desnormalize na dimensão.

4. Tipos de dimensões

Nem toda dimensão é igual. Conhecer estes padrões evita retrabalho depois.

1

Conformed dimensions

Compartilhadas por múltiplas tabelas fato. dim_date, dim_customer usadas por fct_orders, fct_page_views, fct_support_tickets. Essenciais para consistência.

2

Role-playing dimensions

Mesma dimensão usada mais de uma vez com papéis diferentes. dim_date como order_date, ship_date, delivery_date. Use views ou aliases.

3

Dimensões degeneradas

Chaves de dimensão dentro da tabela fato sem dimensão separada. Números de pedido, IDs de fatura, IDs de transação. Nenhum atributo extra para armazenar.

4

Junk dimensions

Combine flags de baixa cardinalidade em uma dimensão única. Em vez de 5 booleanos na tabela fato: dim_order_flags com todas as combinações.

5

Dimensão data

A dimensão mais importante. Atributos pré-calculados: day_of_week, is_weekend, fiscal_quarter, holiday_flag. Use sempre surrogate key inteira (formato YYYYMMDD).

Exemplo: dimensão data

CREATE TABLE dim_date (
    date_sk             INT PRIMARY KEY,       -- formato YYYYMMDD
    date_actual         DATE NOT NULL,
    day_of_week         VARCHAR(10),           -- 'Monday', 'Tuesday', ...
    day_of_week_num     INT,                   -- 1-7
    day_of_month        INT,
    day_of_year         INT,
    week_of_year        INT,
    month_num           INT,
    month_name          VARCHAR(10),
    quarter_num         INT,
    quarter_name        VARCHAR(10),           -- 'Q1', 'Q2', ...
    year_num            INT,
    fiscal_year         INT,
    fiscal_quarter      INT,
    is_weekend          BOOLEAN,
    is_holiday          BOOLEAN,
    holiday_name        VARCHAR(50)
);

-- Uso: filtrar/agrupar facilmente por qualquer atributo de data
SELECT 
    d.month_name,
    d.year_num,
    SUM(f.order_total) as revenue
FROM fct_orders f
JOIN dim_date d ON f.order_date_sk = d.date_sk
WHERE d.is_weekend = FALSE
GROUP BY d.month_name, d.year_num;

5. Slowly Changing Dimensions (SCD)

Atributos de dimensões mudam com o tempo. Cliente muda endereço, produto muda categoria, funcionário troca de equipe. Tratar bem essas mudanças é crucial para manter fidelidade histórica.

Tipo 0: Fixo

Nunca muda. Valor original preservado. Use para atributos imutáveis (data de nascimento, data de adesão).

Exemplo: Canal de aquisição original fica mesmo que o cliente retorne por outro canal.

Tipo 1: Overwrite

Valor antigo é sobrescrito. Sem histórico. Use para correções ou atributos sem valor histórico.

Trade-off: Simples, mas você não sabe em que segmento estava no momento do pedido.

Tipo 2: Nova linha (o mais comum)

Cria uma nova linha de dimensão com nova surrogate key. Marca effective_date, expiry_date e flag is_current. Preserva histórico completo.

Ideal para: Atributos onde a história importa. Mudança de segmento, endereço, pricing tier.

Tipo 3: Nova coluna

Adiciona colunas current_value e previous_value. Histórico limitado (geralmente um valor anterior).

Raro: Útil só para comparações “antes/depois”. Tipo 2 quase sempre é melhor.

Exemplo: SCD Tipo 2

-- SCD Tipo 2: cliente muda de 'SMB' para 'Enterprise'
-- ANTES: 1 linha
customer_sk | customer_id | segment    | effective_date | expiry_date | is_current
1           | C001        | SMB        | 2023-01-01     | 9999-12-31  | TRUE

-- DEPOIS: 2 linhas (antiga expira, nova criada)
customer_sk | customer_id | segment    | effective_date | expiry_date | is_current
1           | C001        | SMB        | 2023-01-01     | 2024-06-15  | FALSE
2           | C001        | Enterprise | 2024-06-15     | 9999-12-31  | TRUE

-- Consulta histórica: qual segmento no momento do pedido?
SELECT 
    o.order_id,
    c.segment as segment_at_order_time
FROM fct_orders o
JOIN dim_customers c ON o.customer_sk = c.customer_sk
-- customer_sk na tabela fato aponta para a versão histórica correta

Armadilha do Tipo 2

No Tipo 2 você decide na carga qual surrogate key associar ao fato. Normalmente quer a versão "current" no momento do evento. Isso exige lookup point-in-time no ETL.

6. Tipos de tabelas fato

Processos diferentes pedem designs diferentes de tabela fato. Escolha o tipo conforme a natureza dos eventos.

Transaction facts

Uma linha por evento no menor grão. O tipo mais comum. Pedidos, cliques, pagamentos, logins.

Grão: Uma linha por item de pedido

Periodic snapshot facts

Uma linha por intervalo de tempo. Captura estado em cadência fixa. Saldos de conta, níveis de estoque, snapshots de pipeline.

Grão: Uma linha por conta por dia

Accumulating snapshot facts

Uma linha por instância de processo, atualizada conforme milestones acontecem. E-commerce fulfillment, pedidos de empréstimo, tickets de suporte.

Grão: Uma linha por pedido (atualizada ao longo do ciclo)

Factless facts

Registram eventos sem medidas—apenas chaves estrangeiras. Presenças de alunos, cobertura de promoções de produto.

Uso: Quem esteve presente? Quais produtos estavam em promoção?

Exemplo: accumulating snapshot (fulfillment)

CREATE TABLE fct_order_fulfillment (
    order_sk                BIGINT PRIMARY KEY,
    order_id                VARCHAR(50),
    customer_sk             BIGINT,
    -- Várias chaves de data (milestones)
    order_date_sk           INT,
    payment_date_sk         INT,
    ship_date_sk            INT,
    delivery_date_sk        INT,
    -- Lags calculados
    days_to_payment         INT,
    days_to_ship            INT,
    days_to_delivery        INT,
    -- Medidas
    order_total             DECIMAL(10,2),
    current_status          VARCHAR(50)
);

-- A linha é atualizada conforme o pedido avança
-- Inicialmente: só order_date_sk
-- Após pagamento: payment_date_sk preenchido, days_to_payment calculado
-- Após envio: ship_date_sk preenchido, days_to_ship calculado
-- Após entrega: delivery_date_sk preenchido, days_to_delivery calculado

7. Boas práticas (checklist)

Defina o grão primeiro

Antes de projetar uma tabela fato, escreva o grão: "Uma linha por item de pedido" ou "Uma linha por cliente por dia". Não misture grãos.

Use surrogate keys

Surrogate keys inteiras em todas as dimensões. Natural keys ficam como atributos. Funcionam bem com SCD Tipo 2 e melhoram joins.

Construa conformed dimensions

dim_date e dim_customer devem ser compartilhadas entre todas as tabelas fato. Mesmas chaves, mesmos atributos. Habilita análise cross-processos.

Desnormalize dimensões

Prefira star a snowflake. Inclua categoria, departamento, região direto na dimensão. Armazenamento é barato; joins não.

Adicione uma dimensão data

Nunca faça join em datas cruas. Crie dim_date com atributos pré-calculados. Chaves inteiras (YYYYMMDD) ajudam no partition pruning.

Trate null com linhas padrão

Crie linhas "Unknown" ou "Not Applicable" nas dimensões (SK = -1). Nada de chaves estrangeiras null nas tabelas fato.

Documente o grão

Toda tabela fato deve documentar o grão. Evita double-counting e ajuda analistas a escrever queries corretas.

SCD Tipo 2 para atributos críticos

Segmento de cliente, categoria de produto, departamento—tudo que muda e impacta análise deve ser Tipo 2.

Dica

O “teste do analista”

Depois do design, peça a um analista para escrever 5 queries comuns sem documentação. Se cada uma leva menos de 5 minutos, o modelo está bom. Se precisam de esclarecimentos ou correções, simplifique.

8. Perguntas frequentes

Diferença entre tabela fato e tabela dimensão?

Tabelas fato guardam eventos mensuráveis (transações, cliques, pedidos) com métricas e chaves estrangeiras. Dimensões guardam atributos descritivos (clientes, categorias de produto, datas) para contexto. Fatos respondem "quanto"; dimensões respondem "quem, o quê, quando, onde, por quê".

Star schema vs snowflake schema?

Star schema tem dimensões desnormalizadas ligadas direto à tabela fato, formando uma estrela. Snowflake normaliza dimensões em subdimensões (produto → categoria → departamento). Star é preferido por ser mais simples e performático.

O que são Slowly Changing Dimensions?

SCD tratam mudanças de atributos. Tipo 1 sobrescreve (sem histórico). Tipo 2 cria nova linha com datas de vigência (histórico completo). Tipo 3 adiciona colunas para valores anteriores (histórico limitado). Tipo 2 é o padrão para precisão histórica.

Surrogate key ou natural key?

Use surrogate key (inteiros gerados) como chave primária nas dimensões. Natural keys (ex.: customer_id) como atributos. Surrogate keys são estáveis, rápidas e funcionam com SCD Tipo 2. Natural keys podem mudar e quebrar joins.

Visualize seu modelo dimensional

Crie diagramas star schema claros com fatos, dimensões e relacionamentos. Documente o modelo para que analistas consultem sem pedir ajuda.