Como diagnosticar erros de modelagem (sintomas, perguntas e evidências)
Erros de modelagem raramente aparecem como “erro de diagrama”. Eles aparecem como sintomas: relatórios inconsistentes, regras difíceis de implementar, duplicidade de dados, integrações frágeis, e necessidade constante de “gambiarras” na aplicação. O diagnóstico começa com perguntas objetivas e evidências no esquema.
- Sintoma: o mesmo dado aparece em lugares diferentes e diverge. Evidência: colunas com mesmo significado em tabelas distintas (ex.:
cliente_nomeem várias tabelas). - Sintoma: registros “somem” em relatórios ou aparecem duplicados. Evidência: relacionamentos N:N sem tabela associativa, ou joins feitos por colunas não-chave.
- Sintoma: regras de negócio ficam “na aplicação” e não no banco. Evidência: ausência de
FOREIGN KEY,UNIQUE,CHECK, defaults e regras de exclusão/atualização. - Sintoma: chaves mudam e quebram integrações. Evidência: chave primária baseada em atributo sujeito a alteração (ex.: e-mail, telefone, placa, matrícula “reutilizável”).
- Sintoma: campos “às vezes são isso, às vezes aquilo”. Evidência: nomes ambíguos (
status,tipo,codigo) e colunas polimórficas (id_referenciasem indicar a qual entidade pertence).
Erros recorrentes e como detectá-los (com correções)
1) Entidades duplicadas (mesma coisa com nomes diferentes)
Como aparece: duas tabelas representam o mesmo conceito, geralmente criadas por times diferentes ou por “módulos” (ex.: Cliente e Consumidor; Usuario e Pessoa com dados semelhantes).
Como detectar:
- Compare colunas: se 60–80% dos atributos são equivalentes, há forte indício de duplicidade.
- Procure chaves naturais iguais (CPF/CNPJ, e-mail, documento) em tabelas diferentes.
- Procure integrações que “mapeiam” uma tabela na outra (ETL/rotinas de sincronização).
Correção típica: consolidar em uma entidade canônica e transformar a outra em visão, tabela de referência, ou especialização (quando realmente houver subtipos).
Ruim: Cliente(id, nome, cpf, email) e Consumidor(id, nome, cpf, email)Bom: Pessoa(id, nome, cpf, email) + papéis (ex.: Cliente, Fornecedor) se necessário2) Atributos repetidos em várias tabelas (redundância “por conveniência”)
Como aparece: para “evitar join”, copia-se o mesmo atributo em tabelas diferentes: nome_cliente em Pedido, Fatura e Entrega.
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
Como detectar:
- Liste colunas com padrões iguais (
nome_*,endereco_*,telefone_*) em múltiplas tabelas. - Verifique se essas colunas são “deriváveis” via relacionamento (ex.:
Pedido→Cliente). - Procure divergências: mesmo
cliente_idcom nomes diferentes em linhas distintas.
Correção típica: manter o atributo no “dono” do dado e referenciar por chave estrangeira. Se houver necessidade de “foto” do dado no tempo (ex.: endereço de entrega no momento do pedido), modelar como snapshot explícito (ex.: PedidoEndereco) e não como duplicação espalhada.
3) Relacionamentos incorretos: N:N sem tabela associativa
Como aparece: tenta-se armazenar múltiplos valores em uma coluna (ex.: produto_ids com lista), ou cria-se duas FKs em uma tabela sem representar o vínculo corretamente.
Como detectar:
- Colunas com listas/CSV/JSON para representar múltiplos IDs.
- Tabelas com colunas repetidas do tipo
produto1_id,produto2_id… - Relatórios que exigem parsing de string para fazer join.
Correção típica: criar tabela associativa com FKs e, quando necessário, atributos do relacionamento (quantidade, preço, papel, ordem).
Ruim: Pedido(id, cliente_id, produto_ids)Bom: Pedido(id, cliente_id) + PedidoItem(pedido_id, produto_id, quantidade, preco_unitario)4) Cardinalidade inferida “no chute” (sem regra explícita)
Como aparece: define-se 1:1, 1:N ou N:N sem evidência. Depois surgem exceções: “um pedido pode ter mais de um pagamento”, “um usuário pode ter vários endereços”, “um produto pode ter múltiplas categorias”.
Como detectar:
- Campos “reserva de futuro” como
pagamento2_id,endereco_extra. - Regras de negócio descritas em texto que conflitam com o modelo (ex.: “parcelamento” vs. tabela com um único pagamento).
- Uso de
UNIQUEem FK sem justificativa (forçando 1:1).
Correção típica: transformar 1:1 em 1:N quando a regra permitir múltiplos; ou manter 1:1, mas com constraint clara e justificativa. A correção deve vir acompanhada de uma regra verificável (ex.: “no máximo 1 pagamento aprovado por pedido”).
5) Chaves primárias instáveis (mudam com o tempo)
Como aparece: usa-se e-mail, telefone, documento “reformatável”, código externo ou qualquer atributo sujeito a alteração como PK.
Como detectar:
- PK com significado de negócio e potencial de mudança (ex.:
email). - Histórico de alterações do atributo (troca de e-mail é comum).
- Integrações que exigem “atualizar PK” em cascata.
Correção típica: usar um identificador estável como PK (surrogate) e manter o atributo natural com UNIQUE (quando aplicável) e regras de validação.
Ruim: Usuario(email PK, nome, ...)Bom: Usuario(id PK, email UNIQUE, nome, ...)6) Ausência de constraints (o banco aceita qualquer coisa)
Como aparece: o modelo “funciona” apenas porque a aplicação tenta validar. Sem constraints, dados inválidos entram por carga, integração, scripts e manutenção.
Como detectar:
- Tabelas sem
FOREIGN KEY(IDs soltos). - Campos que deveriam ser únicos sem
UNIQUE(ex.: número de pedido). - Campos com domínio conhecido sem
CHECK(ex.: status, faixas, limites).
Correção típica: adicionar constraints alinhadas às regras: NOT NULL quando obrigatório, UNIQUE para unicidade, CHECK para domínio, e FKs com ações (ON DELETE/ON UPDATE) coerentes.
Exemplos (genéricos): UNIQUE(numero_pedido), CHECK(quantidade > 0), FOREIGN KEY(cliente_id) REFERENCES Cliente(id)7) Uso inadequado de NULL (NULL como “valor”)
Como aparece: usa-se NULL para representar “não se aplica”, “ainda não informado”, “desconhecido”, “zero”, “falso”, “não cadastrado”, tudo misturado. Isso quebra filtros, agregações e regras.
Como detectar:
- Colunas com muitos NULLs e sem explicação de opcionalidade real.
- Consultas cheias de
COALESCEpara “consertar” relatórios. - Colunas booleanas nulas (três estados) sem regra explícita.
Correção típica:
- Se “não se aplica”, considere separar em subentidade ou usar domínio que represente explicitamente (ex.: status).
- Se “ainda não informado”, avalie fluxo: o dado é obrigatório em algum estágio? Modele o estágio (ex.:
data_aprovacaonula até aprovar) e documente a regra. - Se “desconhecido” é um estado real, use um valor explícito em domínio controlado (ex.:
situacao_endereco).
8) Nomes ambíguos (o modelo não comunica)
Como aparece: colunas e tabelas com nomes genéricos: status, tipo, codigo, data, valor, descricao. Isso gera interpretações diferentes e erros de uso.
Como detectar:
- Ao ler o esquema, você precisa abrir a aplicação para entender o significado.
- O mesmo nome aparece em várias tabelas com significados diferentes.
- Há colunas “curinga” como
observacaousada para dados estruturados.
Correção típica: nomes autoexplicativos e específicos do contexto: status_pedido, tipo_pagamento, data_emissao, valor_total, codigo_rastreio. Quando houver domínio, explicitar (ex.: status_pedido com valores permitidos).
9) Modelagem orientada pela tela (UI-driven) em vez do domínio
Como aparece: tabelas espelham formulários: “CadastroTela1”, “TelaCheckout”, “AbaFinanceiro”. Campos são agrupados por layout, não por significado e dependências.
Como detectar:
- Tabelas com muitos campos heterogêneos e baixa coesão (mistura de dados pessoais, entrega, pagamento, marketing).
- Colunas “temporárias” para etapas do fluxo (ex.:
passo_atual+ dezenas de campos opcionais). - Regras do tipo “se marcou X, então preenche Y” implementadas só no front-end.
Correção típica: separar entidades por responsabilidade (ex.: pessoa/contato/endereço/pedido/pagamento) e representar o fluxo como estados/eventos quando necessário, sem “tabelas de tela”. A UI deve consultar e gravar no modelo do domínio, não ditar sua estrutura.
Conjunto de modelos “ruins” e refatoração passo a passo
A seguir, um exemplo intencionalmente problemático e a refatoração incremental. O objetivo é mostrar como cada ajuste resolve um sintoma específico.
Modelo ruim (exemplo consolidado)
Tabela: Pedido (orientada pela tela de checkout) - id (PK) - numero_pedido - data - cliente_nome - cliente_email - cliente_documento - endereco_entrega - cidade_entrega - cep_entrega - produto_ids -- lista “1,2,3” - quantidades -- lista “2,1,5” - valor_total - status -- valores livres - pagamento_tipo -- texto livre - pagamento_status -- texto livre - pagamento_data - cupom_codigo -- pode repetir sem controleProblemas presentes: atributos repetidos (dados do cliente e endereço no pedido), N:N sem associativa (produtos e quantidades em listas), nomes ambíguos (data, status), ausência de constraints (domínios livres), uso implícito de NULL (pagamento pode não existir), modelagem orientada pela tela (tudo no mesmo lugar).
Refatoração passo 1: separar o “dono” dos dados do cliente
Diagnóstico: cliente_nome, cliente_email, cliente_documento são dados do cliente, não do pedido. Mantê-los no pedido cria divergência quando o cliente atualiza cadastro.
Ajuste: criar entidade Cliente e referenciar no pedido.
Cliente(id PK, nome, email UNIQUE, documento UNIQUE) Pedido(id PK, numero_pedido UNIQUE, data_criacao, cliente_id FK)Lógica do ajuste: o pedido “aponta” para quem fez o pedido; o cadastro do cliente é centralizado. Se o negócio exigir “foto” do cliente no momento da compra (ex.: nota fiscal), isso deve ser modelado como snapshot específico, não como cópia informal.
Refatoração passo 2: corrigir N:N de pedido e produto com tabela associativa
Diagnóstico: produto_ids e quantidades em listas impedem integridade referencial e consultas corretas.
Ajuste: criar Produto e PedidoItem.
Produto(id PK, nome, preco_base) PedidoItem( pedido_id FK, produto_id FK, quantidade CHECK(quantidade > 0), preco_unitario CHECK(preco_unitario >= 0), PRIMARY KEY (pedido_id, produto_id) )Lógica do ajuste: o relacionamento “pedido contém produtos” tem atributos próprios (quantidade, preço no momento). Isso pertence à associativa, não ao pedido nem ao produto.
Refatoração passo 3: tratar endereço de entrega como snapshot explícito
Diagnóstico: endereco_entrega, cidade_entrega, cep_entrega no pedido podem ser snapshot (endereço usado naquela entrega), mas estão misturados e sem estrutura.
Ajuste: criar uma estrutura de endereço de entrega do pedido (snapshot), separada do cadastro de endereços do cliente.
PedidoEndereco( pedido_id PK FK, logradouro, numero, complemento NULL, bairro, cidade, uf, cep )Lógica do ajuste: o endereço de entrega do pedido não deve mudar se o cliente alterar seu endereço cadastral depois. Ao mesmo tempo, não se espalha endereço em colunas soltas no pedido.
Refatoração passo 4: tornar status e tipo domínios controlados (e reduzir ambiguidade de nomes)
Diagnóstico: status, pagamento_tipo, pagamento_status como texto livre geram valores inconsistentes (ex.: “Pago”, “pago”, “PAGO”, “aprovado”).
Ajuste: renomear e restringir domínio via CHECK (ou tabelas de domínio, quando necessário).
Pedido( id PK, numero_pedido UNIQUE, data_criacao, status_pedido CHECK(status_pedido IN ('CRIADO','PAGO','CANCELADO','ENVIADO')), cliente_id FK )Pagamento( id PK, pedido_id FK, tipo_pagamento CHECK(tipo_pagamento IN ('CARTAO','PIX','BOLETO')), status_pagamento CHECK(status_pagamento IN ('PENDENTE','APROVADO','RECUSADO')), data_status )Lógica do ajuste: o banco passa a rejeitar valores inválidos. Além disso, nomes específicos evitam confusão entre “status do pedido” e “status do pagamento”.
Refatoração passo 5: corrigir cardinalidade de pagamento (não inferir sem regra)
Diagnóstico: no modelo ruim, pagamento está “embutido” no pedido, sugerindo 1:1. Mas pode haver mais de uma tentativa (recusado e depois aprovado) ou parcelamento.
Ajuste: decidir com base em regra verificável e refletir no modelo:
- Cenário A (comum): um pedido pode ter várias tentativas de pagamento. Então
Pedido 1:N Pagamento. - Cenário B (restrito): um pedido tem no máximo um pagamento “ativo”. Então manter 1:N, mas impor regra adicional (ex.: unique parcial por status ativo, quando suportado) ou controlar via lógica transacional.
Pagamento(pedido_id FK, id PK, ...)Lógica do ajuste: separar pagamento permite histórico e evita colunas nulas no pedido quando ainda não há pagamento.
Refatoração passo 6: tratar cupom de forma relacional e com constraints
Diagnóstico: cupom_codigo no pedido sem controle permite códigos inválidos e repetição sem regra.
Ajuste: criar Cupom e referenciar; opcionalidade explícita (pode ser nulo no pedido, se cupom for opcional).
Cupom(id PK, codigo UNIQUE, percentual_desconto CHECK(percentual_desconto BETWEEN 0 AND 100)) Pedido(..., cupom_id FK NULL)Lógica do ajuste: o cupom é um conceito reutilizável e validável. O pedido apenas aponta para ele quando aplicável.
Refatoração passo 7: revisar NULLs e obrigatoriedade por coluna (sem “NULL por padrão”)
Diagnóstico: no modelo ruim, muitos campos podem ficar nulos sem critério. Isso cria registros incompletos que “passam” no banco.
Ajuste prático: para cada coluna, decidir: é obrigatório sempre, obrigatório em um estado, ou opcional de verdade?
- Sempre obrigatório:
Pedido.numero_pedido,Pedido.data_criacao,Pedido.cliente_id→NOT NULL. - Obrigatório por estado:
Pagamento.data_statuspode ser obrigatória quandostatus_pagamentonão for PENDENTE (via regra/constraint quando possível). - Opcional real:
Pedido.cupom_idpode serNULLse cupom for opcional.
Lógica do ajuste: NULL deixa de ser “valor padrão” e passa a representar ausência legítima de informação, com significado claro.
Refatoração passo 8: adicionar integridade referencial e unicidade onde faz sentido
Diagnóstico: sem FKs e UNIQUE, o banco aceita cliente_id inexistente, itens sem pedido, número de pedido duplicado etc.
Ajuste: aplicar constraints essenciais.
Pedido.cliente_id REFERENCES Cliente(id) PedidoItem.pedido_id REFERENCES Pedido(id) PedidoItem.produto_id REFERENCES Produto(id) Pagamento.pedido_id REFERENCES Pedido(id) Pedido.numero_pedido UNIQUE Cliente.email UNIQUE Cliente.documento UNIQUELógica do ajuste: o banco passa a proteger o modelo contra dados órfãos e duplicidades estruturais.
Checklist rápido de revisão (para usar em qualquer modelo)
| Erro | Como identificar rápido | Correção típica |
|---|---|---|
| Entidades duplicadas | Duas tabelas com colunas e chaves naturais semelhantes | Consolidar em entidade canônica; especializar se necessário |
| Atributos repetidos | Mesmo dado em várias tabelas; divergência em relatórios | Centralizar no “dono”; snapshot explícito quando for dado histórico |
| N:N sem associativa | Listas em colunas; colunas produto1/produto2 | Tabela associativa com FKs e atributos do vínculo |
| Cardinalidade sem regra | UNIQUE em FK “por hábito”; exceções frequentes | Regras verificáveis + ajuste 1:1/1:N/N:N conforme necessário |
| PK instável | PK é e-mail/documento mutável; cascatas de update | PK estável + UNIQUE no identificador natural |
| Sem constraints | IDs sem FK; domínios livres; duplicidade | FK/UNIQUE/CHECK/NOT NULL alinhados às regras |
| NULL mal usado | Muitos NULLs; boolean nulo; COALESCE em todo lugar | Opcionalidade clara; estados explícitos; separar subentidades |
| Nomes ambíguos | status/tipo/codigo/data/valor sem contexto | Nomes específicos + domínios controlados |
| Orientado pela tela | Tabelas “monstro” espelhando formulários | Reorganizar por domínio; fluxo como estados/eventos quando necessário |