Pular para conteúdo

Modelagem de Dados

Por que um banco de dados (e não uma planilha)?

Uma forma simples de controlar informação (gastos, estoque, clientes, ...) é uma tabela: linhas para cada registro, colunas para cada informação sobre ele. Uma planilha eletrônica resolve isso bem — até o volume de dados crescer, ou até você precisar de relatórios/cálculos mais complexos, consultas rápidas por critério, ou consistência garantida (ex.: dois processos gravando ao mesmo tempo sem conflito).

Definição: SGBD (Sistema de Gerenciamento de Banco de Dados)

Software responsável por armazenar e consultar dados de forma eficiente e segura, sem que a aplicação precise se preocupar com o "como" — só o "o quê" (ver SQL). Exemplos: MySQL, PostgreSQL, Oracle, SQL Server.

Fundamentos de SGBD relacional

Da pasta de arquivos ao banco de dados

Abordagem Como é Problemas
Tradicional (arquivos por sistema) Cada programa tem seus próprios arquivos Duplicidade de dados, inconsistência, difícil relacionar informações
Sistemas integrados Módulos compartilham dados dentro de um mesmo sistema Acoplamento forte; mudar um dado exige mexer em vários programas
Banco de dados Dados centralizados e geridos por um SGBD (Sistema Gerenciador de Banco de Dados), independentes dos programas Exige modelagem e administração

Vantagens do SGBD: controle de redundância, integridade, segurança e permissões, concorrência (vários usuários), recuperação (backup/restore) e linguagem de consulta padronizada.

Níveis de abstração e independência de dados

Definição: abstração de dados

Capacidade de esconder detalhes e mostrar a cada público só o que interessa. A arquitetura clássica (ANSI/SPARC) tem três níveis.

Nível Descreve Quem usa
Físico (interno) Como os dados são armazenados (blocos, índices, arquivos) Administradores de banco
Conceitual (lógico) Quais dados e relações (tabelas, colunas, chaves) Projetistas e DBAs
Visões (externo) Apenas a parte do banco que cada usuário ou aplicação precisa Usuários finais e aplicações (views)
  • Esquema é a descrição da estrutura (muda pouco); instância é o conteúdo do banco em um instante (muda sempre).
  • Independência física: mudar o armazenamento (índices, discos, particionamento) sem alterar o esquema lógico nem os programas.
  • Independência lógica: mudar o esquema lógico (ex.: adicionar colunas) sem quebrar os programas. É mais difícil de alcançar, e as views ajudam.

Linguagens e usuários do SGBD

Sigla Nome Para quê Comandos
DDL Definição de dados Criar e alterar a estrutura CREATE, ALTER, DROP
DML Manipulação de dados Inserir, alterar, excluir, consultar INSERT, UPDATE, DELETE, SELECT
DCL Controle de dados Permissões GRANT, REVOKE
DTL/TCL Controle de transações Confirmar ou desfazer COMMIT, ROLLBACK, SAVEPOINT

Usuários: DBA (administra, segurança e desempenho), projetistas/analistas (modelam), programadores (aplicações) e usuários finais (consultas e telas).

Modelo relacional: chaves, integridade e conversão

  • Chaves: primária (identifica a linha), estrangeira (referencia a primária de outra tabela), alternativa/candidata e composta; índices aceleram buscas.
  • Regras de integridade: de entidade (a chave primária nunca é nula nem repetida) e referencial (toda chave estrangeira aponta para uma chave existente, ou é nula).
  • Do modelo entidade-relacionamento (MER) ao relacional: cada entidade vira uma tabela; relacionamento 1:N coloca a chave estrangeira no lado "N"; N:N vira uma tabela associativa com as duas chaves; 1:1 coloca a chave em um dos lados (ou funde as tabelas); atributo multivalorado vira outra tabela; atributo composto vira colunas (Modelagem).

As regras de Codd

Em 1970, Edgar F. Codd propôs o modelo relacional e, depois, 12 regras (mais a regra zero) para um banco verdadeiramente relacional. As mais citadas:

Regra Em resumo
0 (fundamento) O SGBD gerencia os dados apenas por capacidades relacionais
1 (informação) Toda informação (inclusive os metadados) é representada por valores em tabelas
2 (acesso garantido) Todo dado é acessível por tabela + chave primária + coluna; a ordem das linhas e colunas não importa
3 (valores nulos) NULL representa informação inexistente ou desconhecida, tratada de forma sistemática (não é zero nem vazio)
4 (catálogo dinâmico) O dicionário de dados é armazenado em tabelas e consultável com a mesma linguagem
5 (sublinguagem completa) Uma linguagem (SQL) cobre definição, manipulação, integridade, autorização e transações
6 (atualização de visões) Visões teoricamente atualizáveis devem ser atualizáveis pelo sistema
7 (operações de conjunto) Inserir, alterar e excluir operam sobre conjuntos de linhas
8 e 9 (independência) Física e lógica de dados
10 (independência de integridade) Regras de integridade ficam no catálogo, não nos programas
11 (independência de distribuição) Os dados podem estar distribuídos sem mudar as aplicações
12 (não subversão) Não se pode burlar regras de integridade por uma linguagem de baixo nível

Poucos SGBDs cumprem todas, mas elas definem o que se espera de um banco relacional (ACID e transações).

Tabelas, linhas e colunas

Um banco de dados relacional organiza dados em tabelas — o mesmo conceito de linhas e colunas de uma planilha, mas com um detalhe crucial: cada coluna tem um tipo de dado declarado (número decimal, data, texto, ...), e o banco garante que ninguém insira um valor incompatível com esse tipo.

Cada linha de uma tabela é um registro — uma ocorrência completa daquela entidade (uma compra, uma pessoa, um pedido, ...).

Chave primária

Depois que uma tabela cresce, referenciar uma linha específica por descrição ("aquela compra de lanchonete do dia 5") fica inviável — pode haver várias compras parecidas. É o mesmo problema de identificar uma pessoa (por isso existe o CPF) ou um computador (por isso existe o número de série): precisa de um valor único.

Definição: Chave primária (Primary Key)

Coluna (ou conjunto de colunas) que identifica unicamente cada linha de uma tabela — nunca se repete, nunca fica vazia. A forma mais simples e comum de gerar esse valor é um número inteiro que incrementa automaticamente a cada novo registro (1, 2, 3, ...), convencionalmente chamado de id. Ver SQL para a sintaxe (PRIMARY KEY, AUTO_INCREMENT).

Modelagem é um processo iterativo, não uma etapa única

Modelar uma tabela é decidir quais colunas ela precisa, a partir da conversa com quem vai usar o sistema — não existe um conjunto de regras fixas que garanta "o modelo certo" na primeira tentativa. Um exemplo típico: uma tabela de Aluno para uma escola pode começar simples (id, nome, serie, sala, contato) — mas, ao conversar com a diretoria, aparecem novas necessidades:

  • Precisa contatar os pais, não o aluno → viram novos campos (nome/telefone do pai e da mãe).
  • Precisa saber a data de nascimento para decidir vacinação por idade → campo novo.
  • serie e sala mudam todo ano → os nomes dos campos são ajustados para deixar isso explícito (serieAtual, salaAtual), evitando a leitura errada de que são valores fixos.

A lição prática: um modelo de dados evolui conforme você entende melhor o domínio e os requisitos mudam — refinar o modelo depois de identificar uma lacuna não é sinal de que o modelo original estava "errado", é o processo funcionando como esperado.

Exemplo de trade-off: como representar "inativo"?

Um bom exercício de modelagem: a escola descobre um bug e percebe que precisa saber quando um aluno não está mais ativo (saiu da escola). Como representar isso na tabela Aluno, que tem colunas serieAtual e salaAtual? Três abordagens possíveis, cada uma com uma consequência diferente:

  1. Marcar serieAtual/salaAtual como NULL quando o aluno sai. Simples, mas ambíguo: um NULL também poderia significar "esqueceram de preencher a série de um aluno novo" — as duas situações (inativo x dado faltando) ficam indistinguíveis.
  2. Usar NULL (para "sem valor") e um campo separado ativo (booleano). Resolve a ambiguidade do item 1, mas ainda permite estados inconsistentes — nada impede um registro com salaAtual preenchida e ativo = false ao mesmo tempo (o aluno está na sala ou não está?).
  3. Só o campo ativo, sem nulos. Mais simples de consultar (WHERE ativo = false), mas permite o mesmo tipo de estado estranho do item 2 (sala preenchida num aluno inativo) — só que agora sem o sinal visual do NULL para desconfiar.

Definição: Não existe modelagem sem trade-off

As três abordagens resolvem o problema original e criam, cada uma, um problema técnico diferente. Não há uma resposta certa universal — o que existe é conhecer as desvantagens de cada opção de antemão, para escolher a que gera menos dor para o seu caso específico, e não ser pego de surpresa depois. Alguns bancos oferecem recursos mais avançados para reforçar essas regras (ex.: CHECK em algumas versões), mas variam de SGBD para SGBD — não são um padrão universal do SQL.

Normalização: de coluna solta para tabela relacionada

Um sinal clássico de que uma tabela precisa ser normalizada (dividida em duas ou mais tabelas relacionadas): guardar informação de uma outra entidade como colunas soltas. Por exemplo, uma tabela compras com colunas comprador e telefone diretamente nela, em vez de numa tabela compradores própria.

O problema não é estético — é de integridade. Guardando o nome do comprador como texto livre repetido em cada compra:

  • Inconsistência: nada impede 'João da Silva' numa linha e 'joão da silva' (grafia diferente) noutra — para o banco, são dois compradores diferentes, mesmo sendo a mesma pessoa na vida real.
  • Redundância: o telefone do mesmo comprador é repetido em toda compra dele — se ele troca de telefone, é preciso atualizar todas as suas compras, uma por uma (fácil de esquecer alguma).

A solução: extrair o comprador para sua própria tabela (com sua própria chave primária), e guardar, na tabela compras, só uma referência a esse comprador — uma coluna com o id dele:

CREATE TABLE compradores (
    id INT PRIMARY KEY AUTO_INCREMENT,
    nome VARCHAR(200),
    endereco VARCHAR(200),
    telefone VARCHAR(30)
);

ALTER TABLE compras ADD COLUMN id_compradores INT;

Definição: Foreign Key (chave estrangeira)

Coluna que guarda o valor da chave primária de outra tabela, criando um relacionamento entre as duas. Ver SQL para a constraint que faz o banco impor essa regra sozinho (FOREIGN KEY ... REFERENCES ...).

Formas normais: 1FN, 2FN e 3FN

Normalizar é organizar o banco para reduzir repetições e inconsistências dividindo as informações em tabelas relacionadas. As formas normais são regras progressivas — cada uma pressupõe a anterior. Cuidado: normalizar sem perder a clareza do modelo.

Forma Regra Problema que resolve
1FN Cada campo guarda um único valor (valores atômicos); uma coluna não contém listas; cada linha é identificada por uma chave primária Coluna telefones com '9999, 8888'
2FN Já está em 1FN e cada dado depende da chave completa (relevante com chave composta) Em itens_pedido(pedido_id, produto_id, nome_produto), nome_produto depende só de produto_id
3FN Já está em 2FN e não há dependência entre colunas não-chave cidade depende de cep, e não diretamente do cliente

Como resolver:

  • 1FN: criar uma tabela separada para os valores repetidos (ex.: tabela telefones com cliente_id e telefone, uma linha por telefone).
  • 2FN: mover o dado para a tabela que representa a entidade a que ele pertence (ex.: nome_produto vai para produtos).
  • 3FN: criar uma tabela própria para a informação relacionada (ex.: tabela de cep com cidade), de modo que cada fato seja armazenado em um só lugar.

Benefícios: atualizações mais simples e seguras, menos repetição e menor risco de inconsistência. (Exemplo prático da primeira etapa em Normalização: de coluna solta para tabela relacionada.)

One to Many / Many to One: de que lado fica a Foreign Key?

Um comprador pode ter várias compras; uma compra pertence a um único comprador. É essa assimetria — vista de um lado é "um para muitos" (one to many), vista do outro é "muitos para um" (many to one) — que determina de que lado a chave estrangeira deve ficar.

Definição: A Foreign Key sempre fica no lado 'muitos'

Regra prática que resolve a confusão de "quem referencia quem": a coluna de chave estrangeira vive na tabela que representa o lado "muitos" da relação (compras.id_compradores, referenciando compradores.id) — nunca o contrário. Colocar o id da compra dentro de compradores obrigaria cada comprador a ter no máximo uma compra (uma única coluna só guarda um valor por vez), o que contradiz a realidade que estamos modelando.

Many to Many: quando nenhum dos dois lados basta

Nem toda relação é "um para muitos". Considere alunos e cursos: um aluno pode se matricular em vários cursos, e um curso tem vários alunos matriculados. Não dá para colocar a chave estrangeira só de um lado — nenhuma tabela sozinha (aluno nem curso) tem como guardar "vários valores" numa única coluna.

A solução padrão é uma tabela associativa (ou tabela de junção): uma terceira tabela, cuja única (ou principal) função é guardar pares de chaves estrangeiras, uma para cada lado da relação:

CREATE TABLE matricula (
    id INT PRIMARY KEY AUTO_INCREMENT,
    aluno_id INT NOT NULL,
    curso_id INT NOT NULL,
    data DATETIME NOT NULL,
    -- FOREIGN KEY (aluno_id) REFERENCES aluno (id),
    -- FOREIGN KEY (curso_id) REFERENCES curso (id)
);

Cada linha de matricula representa uma matrícula específica — um aluno específico, num curso específico. Um mesmo aluno aparece em várias linhas (uma por curso em que está matriculado), e um mesmo curso também aparece em várias linhas (uma por aluno matriculado nele) — é assim que a tabela associativa viabiliza o "muitos para muitos" sem duplicar dado nenhum de aluno ou curso.