Buscar K
Aparência
Aparência
Este guia explica os conceitos fundamentais de modelagem de dados no Horus DataViz:
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.
Conectar duas Tabelas Fato diretamente causa explosão cartesiana, um problema técnico sério que deve ser evitado.
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:
| Vendas | Metas | Resultado 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:
Execute cada aba para ver o problema e entender por que o JOIN direto entre fatos multiplica os valores.
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ão | Exemplo de Uso |
|---|---|
| Calendário | Vendas por data ↔ Metas por data |
| Filial | Vendas por filial ↔ Estoque por filial |
| Produto | Vendas por produto ↔ Compras por produto |
| Vendedor | Vendas por vendedor ↔ Metas por vendedor |
| Cliente | Vendas 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.
Quando existem Tabelas Fato como Vendas e Metas, cada uma possui sua própria coluna de data:
DATA_VENDADATA_METAO 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:
Calendário.DATA = Vendas.DATA_VENDACalendário.DATA = Metas.DATA_METAIMPORTANT
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.
Considere este modelo para análise de metas de vendas:
Com este modelo, é possível criar Expressões como:
-- Percentual de meta atingida
SUM([Vendas].VALOR) / SUM([Metas].VALOR_META) * 100E utilizar no eixo X:
Calendário.DATA — funciona para ambas as Tabelas FatoCalendário.MES — agrupamento mensalVendedor.NOME — por vendedorVendas.DATA_VENDA — funciona apenas para a Tabela Vendas; a Tabela Metas ficaria sem dadosCom Dimensões Compartilhadas, tanto o agrupamento por data quanto por vendedor funcionam corretamente — sem duplicação de valores.
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:
Calendário.DATA usam DATA_EMISSAO automaticamente (a chave padrão).use():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.
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:
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:
| Vendedor | Produto | Total Vendas | Valor Meta |
|---|---|---|---|
| João | Notebook | R$ 5.000 | R$ 0 |
| João | Monitor | R$ 3.200 | R$ 0 |
| João | - | R$ 0 | R$ 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:
| Vendedor | Total Vendas | Valor Meta |
|---|---|---|
| João | R$ 8.200 | R$ 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:
Produto so existe em Vendas, nao em Metas. Veja o que acontece ao inclui-lo no relatorio e como resolver.
É 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:
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.
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:
Como funciona:
Alinhe as colunas em comum, e então preencha as colunas específicas de cada origem com valores neutros (0 ou null):
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 METASO resultado é uma Tabela unificada:
| FATO | DATA | PRODUTO | NUMERO_NF | VALOR_VENDA | VALOR_META |
|---|---|---|---|---|---|
| Venda | 2024-01-15 | Notebook | NF-001 | 3500 | 0 |
| Venda | 2024-01-15 | Monitor | NF-002 | 800 | 0 |
| Meta | 2024-01-01 | Notebook | null | 0 | 5000 |
| Meta | 2024-01-01 | Monitor | null | 0 | 1000 |
Com essa estrutura, é possível criar Expressões simples:
-- 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) * 100TIP
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:
Veja como unificar Vendas e Metas em uma unica tabela e calcular metricas cruzadas sem explosao cartesiana.
| Abordagem | Vantagens | Desvantagens |
|---|---|---|
| Dimensões Compartilhadas | Modelo mais flexível e extensível; cada Tabela Fato mantém suas colunas específicas | Requer criar e manter Tabelas Dimensão (Calendário, etc.) |
| Tabela Fato Única | Modelo mais simples; facilita cálculos entre as origens | Menos flexível para adicionar novas fontes; colunas específicas ficam esparsas |