Erros comuns em Modelagem de Dados: diagnóstico e correções

Capítulo 14

Tempo estimado de leitura: 13 minutos

+ Exercício

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_nome em 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_referencia sem 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ário

2) 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.

Continue em nosso aplicativo e ...
  • Ouça o áudio com a tela desligada
  • Ganhe Certificado após a conclusão
  • + de 5000 cursos para você explorar!
ou continue lendo abaixo...
Download App

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.: PedidoCliente).
  • Procure divergências: mesmo cliente_id com 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 UNIQUE em 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 COALESCE para “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_aprovacao nula 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 observacao usada 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 controle

Problemas 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_idNOT NULL.
  • Obrigatório por estado: Pagamento.data_status pode ser obrigatória quando status_pagamento não for PENDENTE (via regra/constraint quando possível).
  • Opcional real: Pedido.cupom_id pode ser NULL se 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 UNIQUE

Ló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)

ErroComo identificar rápidoCorreção típica
Entidades duplicadasDuas tabelas com colunas e chaves naturais semelhantesConsolidar em entidade canônica; especializar se necessário
Atributos repetidosMesmo dado em várias tabelas; divergência em relatóriosCentralizar no “dono”; snapshot explícito quando for dado histórico
N:N sem associativaListas em colunas; colunas produto1/produto2Tabela associativa com FKs e atributos do vínculo
Cardinalidade sem regraUNIQUE em FK “por hábito”; exceções frequentesRegras verificáveis + ajuste 1:1/1:N/N:N conforme necessário
PK instávelPK é e-mail/documento mutável; cascatas de updatePK estável + UNIQUE no identificador natural
Sem constraintsIDs sem FK; domínios livres; duplicidadeFK/UNIQUE/CHECK/NOT NULL alinhados às regras
NULL mal usadoMuitos NULLs; boolean nulo; COALESCE em todo lugarOpcionalidade clara; estados explícitos; separar subentidades
Nomes ambíguosstatus/tipo/codigo/data/valor sem contextoNomes específicos + domínios controlados
Orientado pela telaTabelas “monstro” espelhando formuláriosReorganizar por domínio; fluxo como estados/eventos quando necessário

Agora responda o exercício sobre o conteúdo:

Ao identificar que um pedido armazena "produto_ids" e "quantidades" em listas dentro da mesma tabela, qual correção de modelagem tende a resolver o problema de integridade e permitir consultas corretas?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

Listas de IDs em uma coluna indicam um relacionamento N:N mal modelado. A solução é criar uma tabela associativa com FKs (ex.: PedidoItem) e colocar nela os atributos do vínculo (quantidade, preço), garantindo integridade e consultas consistentes.

Próximo capitúlo

Boas práticas de documentação da Modelagem de Dados: dicionário, glossário e rastreabilidade

Arrow Right Icon
Capa do Ebook gratuito Modelagem de Dados do Zero: Entidades, Relacionamentos e Regras de Negócio
88%

Modelagem de Dados do Zero: Entidades, Relacionamentos e Regras de Negócio

Novo curso

16 páginas

Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.