Skip to content

Modelagem de Dados

Este guia explica os conceitos fundamentais de modelagem de dados no Horus DataViz:

  • Como conectar Tabelas corretamente
  • Como evitar problemas comuns
  • Como escolher a melhor abordagem para cada cenário
  • Como reaproveitar uma mesma Dimensão Calendário para várias datas do mesmo Fato (Relacionamento Fantasma)

TIP

Para o passo a passo de como criar Relacionamentos na interface, consulte Aba Tabelas (Modelo de Dados).

TIP

Para montar o fluxo completo de ponta a ponta (filtro da dashboard, propagação e datas diferentes por widget), siga a receita Calendário e Filtros Dinâmicos.


Explosão Cartesiana

Conectar duas Tabelas Fato diretamente causa explosão cartesiana, um problema técnico sério que deve ser evitado.

O que é?

Todo Relacionamento no DataViz é um-para-muitos (1:N), pois a Tabela de origem deve ter chaves únicas. Tabelas Fato (Vendas, Metas, Pedidos) contêm eventos repetidos: nenhuma delas possui chaves únicas para a outra. Ao tentar conectar duas Tabelas Fato diretamente, o sistema bloqueará no processo de validação — mas mesmo que fosse possível, o resultado seria um produto cartesiano: cada registro de uma Tabela combinado com cada registro da outra. Com 1.000 vendas e 500 metas, o resultado teria 500.000 linhas — e os valores seriam multiplicados incorretamente.

Exemplo do problema:

VendasMetasResultado Incorreto
3 registros (R$ 300)2 registros (R$ 100)6 registros (R$ 600 de vendas, R$ 300 de metas)

Para visualizar o problema na prática, execute as consultas abaixo e compare os resultados:

Explosao Cartesiana na PraticaDuckDB

Execute cada aba para ver o problema e entender por que o JOIN direto entre fatos multiplica os valores.

Inicializando DuckDB...

Dimensões Compartilhadas

A solução é conectar as Tabelas Fato através de Dimensões Compartilhadas — Tabelas às quais ambas as Tabelas Fato fazem referência. Cada Tabela Fato se relaciona de forma independente com a Tabela Dimensão, evitando a multiplicação dos dados.

Esse padrão funciona para qualquer Dimensão que as Tabelas Fato tenham em comum:

DimensãoExemplo de Uso
CalendárioVendas por data ↔ Metas por data
FilialVendas por filial ↔ Estoque por filial
ProdutoVendas por produto ↔ Compras por produto
VendedorVendas por vendedor ↔ Metas por vendedor
ClienteVendas por cliente ↔ Devoluções por cliente

TIP

O Calendário é a Dimensão mais comum porque praticamente todo dado corporativo possui alguma referência temporal (data da venda, data da meta, data do pedido, etc.). Por isso, ter uma Tabela Calendário bem estruturada é fundamental para qualquer modelo analítico.

Por que usar uma Tabela Calendário?

Quando existem Tabelas Fato como Vendas e Metas, cada uma possui sua própria coluna de data:

  • Vendas: DATA_VENDA
  • Metas: DATA_META

O problema surge ao criar um gráfico comparando as duas:

Ao tentar configurar a coluna DATA_VENDA no eixo X de um gráfico de "% de Meta Atingida", os dados de Metas não conseguem se referenciar a essa coluna — ela pertence apenas à Tabela Vendas. O resultado apresentará dados incorretos ou um erro.

A solução é criar uma Tabela Calendário como ponte. Tanto Vendas quanto Metas se conectam ao Calendário através de suas respectivas colunas de data:

Agora, ao utilizar Calendário.DATA no eixo X:

  • O sistema filtra Vendas pelo Relacionamento Calendário.DATA = Vendas.DATA_VENDA
  • O sistema filtra Metas pelo Relacionamento Calendário.DATA = Metas.DATA_META
  • Ambos os valores aparecem corretamente alinhados por data

IMPORTANT

Regra de ouro: Todas as Dimensões em comum entre Tabelas Fato devem ser conectadas através de Tabelas Dimensão. No exemplo acima, tanto Calendário quanto Vendedor conectam as duas Tabelas Fato, permitindo análises cruzadas por data e por vendedor.

Exemplo Prático: Meta vs. Realizado

Considere este modelo para análise de metas de vendas:

Com este modelo, é possível criar Expressões como:

sql
-- Percentual de meta atingida
SUM([Vendas].VALOR) / SUM([Metas].VALOR_META) * 100

E utilizar no eixo X:

  • Calendário.DATA — funciona para ambas as Tabelas Fato
  • Calendário.MES — agrupamento mensal
  • Vendedor.NOME — por vendedor
  • Vendas.DATA_VENDA — funciona apenas para a Tabela Vendas; a Tabela Metas ficaria sem dados

Com Dimensões Compartilhadas, tanto o agrupamento por data quanto por vendedor funcionam corretamente — sem duplicação de valores.


Relacionamento Fantasma (múltiplas datas no mesmo Fato)

Um mesmo Fato costuma ter mais de uma coluna de data. Uma nota fiscal tem data de emissão e data de entrega; uma oportunidade tem data de abertura, de ganho e de perda. As perguntas "faturamento por emissão" e "faturamento por entrega" usam o mesmo Fato, porém datas diferentes.

A solução é manter uma única Tabela Calendário e ligá-la ao Fato por mais de uma chave. A primeira ligação é a padrão; as demais ficam disponíveis sob demanda. Esse padrão é chamado de Relacionamento Fantasma (ou Relacionamento Secundário).

Com as duas chaves cadastradas:

  • O filtro e o agrupamento por Calendário.DATA usam DATA_EMISSAO automaticamente (a chave padrão).
  • Um Widget ou Expressão que precise da entrega chama a chave alternativa com use():
sql
use([Calendário], [Faturamento].DATA_ENTREGA) SUM([Faturamento].VALOR)

TIP

O cadastro das chaves alternativas é feito na Aba Tabelas. A sintaxe use() e o vídeo tutorial estão em Expressões.

Para o passo a passo completo desse cenário (incluindo o filtro da dashboard e a granularidade por Widget), veja a receita Calendário e Filtros Dinâmicos.


Dimensões Exclusivas e Linhas Duplicadas

Ao combinar métricas de duas ou mais Tabelas Fato no mesmo Relatório, é importante que todas as Dimensões (colunas de agrupamento) utilizadas existam em todas as Tabelas Fato envolvidas. Quando uma Dimensão existe em apenas uma das Tabelas Fato, o resultado pode apresentar linhas aparentemente duplicadas para o mesmo registro.

Para entender o motivo, é importante saber como o sistema combina duas Tabelas Fato: ele gera uma consulta independente para cada uma e depois une os resultados. Cada consulta só consegue preencher as Dimensões das Tabelas que estão conectadas a ela.

Exemplo prático:

Imagine o modelo abaixo. A Tabela Vendas se conecta a Vendedor, Calendário e Produto. A Tabela Metas se conecta apenas a Vendedor e Calendário. Note que não existe conexão entre Metas e Produto, porque as metas são definidas por vendedor/mês, sem granularidade de produto.

No diagrama abaixo, Produto se conecta apenas a Vendas. A ausência de uma ligação entre Produto e Metas é intencional e é a causa do comportamento descrito a seguir.

Se o Relatório solicita Vendedor, Produto, Total Vendas e Valor Meta, o sistema gera duas consultas:

  1. Consulta de Vendas: preenche Vendedor e Produto normalmente, porque ambas as Dimensões estão conectadas à Tabela Vendas
  2. Consulta de Metas: preenche Vendedor, mas não tem acesso a Produto porque não existe essa conexão no modelo

Ao unir os resultados, o sistema agrupa por todas as Dimensões do Relatório (Vendedor + Produto). Como a consulta de Metas não consegue preencher Produto, esse campo fica vazio nessa parte dos dados:

VendedorProdutoTotal VendasValor Meta
JoãoNotebookR$ 5.000R$ 0
JoãoMonitorR$ 3.200R$ 0
João-R$ 0R$ 8.000

João aparece em três linhas: duas com Produto preenchido (proveniente de Vendas, uma para cada produto) e uma terceira sem Produto (proveniente de Metas). Como Notebook, Monitor e - são valores distintos na coluna Produto, o agrupamento não consegue consolidá-los em uma única linha.

Se o Relatório solicitasse apenas Vendedor, Total Vendas e Valor Meta (sem Produto), o resultado seria uma única linha por vendedor, com os valores consolidados:

VendedorTotal VendasValor Meta
JoãoR$ 8.200R$ 8.000

Regra geral: ao montar Relatórios que cruzam métricas de duas Tabelas Fato, utilize apenas Dimensões que existam em ambas. No diagrama acima, as Dimensões seguras seriam Vendedor e Calendário (conectadas a Vendas e Metas). Produto causaria a duplicação porque só existe na Tabela Vendas.

NOTE

Esse comportamento é inerente à diferença de granularidade entre as Tabelas Fato, não sendo um bug do sistema. Se a Tabela Metas não possui a Dimensão Produto, nenhuma abordagem conseguirá realizar o cruzamento de metas com produtos de forma precisa. Na abordagem de Tabela Fato Única, o comportamento se manifesta de outra forma: as linhas de Metas teriam Produto = vazio, e ao filtrar por um produto específico, as metas seriam excluídas do resultado. A recomendação é a montagem de Relatórios que cruzam múltiplas Tabelas Fato, utilizando apenas as Dimensões compartilhadas entre elas.

Execute o exemplo abaixo para ver como e por que as linhas duplicam — e como evitar:

Dimensao Exclusiva = Linhas DuplicadasDuckDB

Produto so existe em Vendas, nao em Metas. Veja o que acontece ao inclui-lo no relatorio e como resolver.

Inicializando DuckDB...

Gerando uma Tabela Calendário

É possível criar uma Tabela Calendário no HorusETL utilizando o nó Python (Pandas). O script abaixo é um exemplo de como gerar um Calendário completo dos últimos 5 anos:

python
import pandas as pd
from datetime import datetime, timedelta

# Configuração: últimos 5 anos até hoje
end_date = datetime.now().date()
start_date = end_date - timedelta(days=5*365)

# Gera range de datas
dates = pd.date_range(start=start_date, end=end_date, freq='D')

# Cria DataFrame do calendário
df = pd.DataFrame({'DATA': dates})

# Adiciona colunas úteis para análise
df['ANO'] = df['DATA'].dt.year
df['MES'] = df['DATA'].dt.month
df['DIA'] = df['DATA'].dt.day
df['DIA_SEMANA'] = df['DATA'].dt.day_name()
df['DIA_SEMANA_NUM'] = df['DATA'].dt.dayofweek + 1  # 1=Segunda, 7=Domingo
df['SEMANA_ANO'] = df['DATA'].dt.isocalendar().week.astype(int)
df['TRIMESTRE'] = df['DATA'].dt.quarter
df['SEMESTRE'] = ((df['DATA'].dt.month - 1) // 6) + 1
df['ANO_MES'] = df['DATA'].dt.to_period('M').astype(str)
df['FIM_SEMANA'] = df['DIA_SEMANA_NUM'].isin([6, 7])

# Formata a coluna DATA para o padrão do DW
df['DATA'] = df['DATA'].dt.strftime('%Y-%m-%d')

# Converte nomes dos dias para português
dias_pt = {
    'Monday': 'Segunda-feira',
    'Tuesday': 'Terça-feira',
    'Wednesday': 'Quarta-feira',
    'Thursday': 'Quinta-feira',
    'Friday': 'Sexta-feira',
    'Saturday': 'Sábado',
    'Sunday': 'Domingo'
}
df['DIA_SEMANA'] = df['DIA_SEMANA'].map(dias_pt)

# Converte colunas para MAIÚSCULO (convenção HorusDW)
df.columns = [col.upper() for col in df.columns]

print(f"Calendário gerado: {len(df)} dias ({start_date} até {end_date})")

TIP

O script pode ser adaptado para incluir feriados, anos fiscais personalizados, ou qualquer outra lógica específica do negócio.


Tabela Fato Única (Abordagem Alternativa)

Outra estratégia para evitar explosão cartesiana é unificar múltiplas Tabelas Fato em uma única Tabela. Por exemplo, ao invés de relacionar Vendas e Metas através de Dimensões, os dados são centralizados em uma Fato Única.

Essa abordagem é especialmente útil quando:

  • As Tabelas possuem granularidades diferentes (vendas diárias vs. metas mensais)
  • O objetivo é simplificar o modelo, eliminando Relacionamentos complexos
  • As Tabelas têm estruturas muito semelhantes

Como funciona:

Alinhe as colunas em comum, e então preencha as colunas específicas de cada origem com valores neutros (0 ou null):

sql
SELECT 'Venda' AS FATO, DATA, PRODUTO, NUMERO_NF, VALOR AS VALOR_VENDA, 0 AS VALOR_META
FROM VENDAS
UNION ALL
SELECT 'Meta' AS FATO, DATA, PRODUTO, NULL AS NUMERO_NF, 0 AS VALOR_VENDA, META AS VALOR_META
FROM METAS

O resultado é uma Tabela unificada:

FATODATAPRODUTONUMERO_NFVALOR_VENDAVALOR_META
Venda2024-01-15NotebookNF-00135000
Venda2024-01-15MonitorNF-0028000
Meta2024-01-01Notebooknull05000
Meta2024-01-01Monitornull01000

Com essa estrutura, é possível criar Expressões simples:

sql
-- Total de vendas
SUM([FatoUnificada].VALOR_VENDA)

-- Total de metas
SUM([FatoUnificada].VALOR_META)

-- % de meta atingida
SUM([FatoUnificada].VALOR_VENDA) / SUM([FatoUnificada].VALOR_META) * 100

TIP

No HorusETL, utilize o nó Concatenar (Union) para unir os fluxos. Ele alinha automaticamente as colunas por nome e preenche com null as colunas que existem apenas em um dos inputs. Para transformações SQL mais avançadas, utilize o nó SQL (DuckDB).

Teste a abordagem completa — do UNION ALL até os cálculos de meta:

Fato Unica: UNION ALL na PraticaDuckDB

Veja como unificar Vendas e Metas em uma unica tabela e calcular metricas cruzadas sem explosao cartesiana.

Inicializando DuckDB...

Quando usar cada abordagem?

AbordagemVantagensDesvantagens
Dimensões CompartilhadasModelo mais flexível e extensível; cada Tabela Fato mantém suas colunas específicasRequer criar e manter Tabelas Dimensão (Calendário, etc.)
Tabela Fato ÚnicaModelo mais simples; facilita cálculos entre as origensMenos flexível para adicionar novas fontes; colunas específicas ficam esparsas