Trilha 12 · Bancos de dados

O modelo relacional

Em 1970, Edgar Codd propôs guardar dados em tabelas, sem que o programa precisasse saber como elas estavam gravadas no disco. A ideia parecia lenta demais para a época e foi resistida por anos. Cinquenta anos depois, ela ainda é a base de quase todo sistema sério do mundo — porque resolve um problema que nunca envelheceu: impedir que os dados se contradigam.

① Intuição

Uma planilha que se recusa a mentir

Todo mundo já viu a planilha do inferno: a coluna "cliente" repetida em 4 mil linhas, com "Ana Souza", "Ana Sousa" e "ana souza" convivendo como se fossem pessoas diferentes. O telefone atualizado em 30 linhas e esquecido em 12. Ninguém consegue mais dizer qual é o número certo — não porque alguém foi descuidado, mas porque a estrutura permitia a contradição.

O modelo relacional resolve isso com uma disciplina simples: cada fato mora em exatamente um lugar. O nome da Ana existe numa linha, numa tabela de clientes. Todo o resto do banco se refere a ela por uma chave. Se o nome muda, muda num lugar só — e é logicamente impossível ficar inconsistente.

O que o modelo relacional realmente entrega: não é velocidade nem elegância — é garantia. As restrições que você declara (chave primária, chave estrangeira, NOT NULL, UNIQUE, CHECK) são verificadas pelo banco em toda escrita, venha ela da sua API, de um script de migração, de um estagiário no console ou de um sistema legado que ninguém mais mantém. É uma garantia que nenhuma validação no código da aplicação consegue dar, porque a aplicação nunca é a única porta de entrada.
② Visualização interativa

Chaves, integridade e normalização

Na primeira aba, clique num autor para ver a relação um-para-muitos e tente operações que violam a integridade. Na segunda, compare a planilha crua com sua versão normalizada e veja cada anomalia que a repetição causa.

Uma chave primária 🔑 identifica cada linha de forma única. Uma chave estrangeira 🔗 é uma coluna que aponta para a chave primária de outra tabela — é assim, e só assim, que tabelas se relacionam. Clique num autor para ver seus livros.

autores
id 🔑nomepais
1Ada LovelaceReino Unido
2Alan TuringReino Unido
3Grace HopperEUA
livros
id 🔑tituloautor_id 🔗ano
10Notas sobre a máquina analítica11843
11Computing Machinery21950
12On Computable Numbers21936
13COBOL: uma história31959
Clique numa linha de autores para ver a relação um-para-muitos em ação.
Integridade referencial

O banco recusa operações que deixariam uma chave estrangeira apontando para o nada. É isso que impede seus dados de ficarem inconsistentes.

③ Explicação técnica

Declarando o esquema

-- Criar tabelas é declarar REGRAS, não só guardar dados.
-- Cada restrição abaixo é uma classe inteira de bug que se torna impossível.

CREATE TABLE autores (
    id      SERIAL PRIMARY KEY,        -- único e nunca nulo
    nome    TEXT NOT NULL,             -- não aceita ausência
    email   TEXT UNIQUE,               -- não aceita repetição
    pais    TEXT DEFAULT 'Brasil'
);

CREATE TABLE livros (
    id         SERIAL PRIMARY KEY,
    titulo     TEXT NOT NULL,
    ano        INTEGER CHECK (ano > 1400),  -- validação de domínio
    autor_id   INTEGER NOT NULL
               REFERENCES autores(id)     -- CHAVE ESTRANGEIRA
               ON DELETE RESTRICT          -- recusa apagar autor com livros
);

-- A partir daqui, o BANCO garante que:
--   . não existem dois autores com o mesmo id
--   . não existe livro sem título
--   . não existe livro de 1200
--   . não existe livro apontando para um autor inexistente
-- Nenhuma dessas garantias depende do código da aplicação estar correto.

Tipos, e o caso especial do NULL

-- Tipos não são burocracia: são a primeira linha de defesa dos dados.

-- ✗ O erro clássico: dinheiro como float
preco REAL          -- 0.1 + 0.2 = 0.30000000000000004
                     -- centavos somem em relatórios financeiros
-- ✓ Use decimal de precisão exata, ou inteiros de centavos
preco NUMERIC(10, 2)
preco_centavos BIGINT

-- ✗ Data como texto: "12/03/2024" é 12 de março ou 3 de dezembro?
--   Ordenar por essa coluna dá ordem alfabética, não cronológica.
data TEXT
-- ✓ Tipo de data de verdade — com fuso, sempre que houver usuários reais
data TIMESTAMPTZ

-- NULL é o valor mais mal-entendido do SQL. Ele significa
-- "desconhecido", não "vazio" nem "zero". E contamina comparações:
SELECT NULL = NULL;      -- → NULL (não é TRUE!)
SELECT NULL <> 5;        -- → NULL
SELECT count(email) ...   -- ignora os NULLs silenciosamente

-- Por isso se testa com IS NULL / IS NOT NULL, nunca com = NULL.
WHERE email IS NULL
Chave natural ou artificial? Você pode usar o CPF como chave primária de uma tabela de pessoas (chave natural) ou um id numérico sem significado (chave artificial, ou surrogate). A prática dominante é a artificial, por um motivo que só aparece depois: chaves naturais mudam. CPFs são digitados errado e corrigidos, e-mails trocam, códigos de produto são reorganizados numa fusão de empresas. Quando a chave muda, toda chave estrangeira que aponta para ela precisa mudar junto. Um id que não significa nada nunca precisa ser corrigido.

Normalização, sem o jargão

-- As formas normais, em português direto:

-- 1FN: nada de listas dentro de uma célula.
--   ✗ produtos = "Teclado, Mouse"
--   ✓ uma linha por produto, numa tabela itens_pedido

-- 2FN: numa chave composta, toda coluna depende da chave INTEIRA.
--   ✗ itens(pedido_id, produto_id, quantidade, nome_produto)
--     nome_produto depende só de produto_id, não do par
--   ✓ o nome do produto mora na tabela produtos

-- 3FN: nenhuma coluna depende de outra coluna NÃO-chave.
--   ✗ funcionarios(id, nome, depto_id, nome_depto)
--     nome_depto depende de depto_id, não do id do funcionário
--   ✓ o nome do departamento mora na tabela departamentos

-- A regra prática que cobre as três, atribuída a Bill Kent:
-- "cada coluna não-chave descreve a chave, a chave inteira,
--  e nada além da chave."

-- E quando NÃO normalizar?
-- Desnormalizar é duplicar dado de propósito para evitar JOINs caros.
-- É uma troca legítima em leitura pesada — mas é uma DECISÃO, tomada
-- depois de medir, e vem com a obrigação de manter as cópias em sincronia.
-- Normalize primeiro. Desnormalize quando o perfil de carga provar que precisa.
④ Projeto para programar

Modelando de verdade

Mini projeto: pegue uma planilha real e desnormalizada — pode ser seu controle de gastos, uma lista de filmes assistidos ou um export de qualquer sistema — e desenhe o esquema normalizado. Crie as tabelas no SQLite (que já vem com o Python, via import sqlite3) com todas as restrições e importe os dados. Anote quantas linhas foram rejeitadas por violar alguma restrição: cada uma era um erro silencioso que a planilha aceitava sem reclamar.

Projeto principal: modele o banco de uma biblioteca com empréstimos. Entidades: livros, exemplares (várias cópias do mesmo livro), leitores e empréstimos. Você vai precisar de uma relação muitos-para-muitos entre livros e autores — que só se resolve com uma terceira tabela, a tabela de junção livro_autor(livro_id, autor_id). Escreva também as restrições que impedem emprestar um exemplar já emprestado.

Desafio extra: modele um histórico versionado. Em vez de UPDATE em cima do dado, cada alteração vira uma linha nova com valido_de e valido_ate, e o registro atual é o que tem valido_ate IS NULL. Isso se chama slowly changing dimension e é como se responde "qual era o preço deste produto em março?". Descubra por que isso complica as chaves estrangeiras — e como resolver.

⑤ Exercícios rápidos

Teste sua intuição

O que é uma chave estrangeira?
Qual é o objetivo principal da normalização?
Em SQL, qual é o resultado de NULL = NULL?
Como se representa uma relação muitos-para-muitos (um livro tem vários autores e um autor tem vários livros)?
⑥ Aplicações no mundo real

Onde você encontra isso

🏦

Sistemas bancários e o legado que não cai

Boa parte do núcleo bancário mundial roda sobre bancos relacionais desenhados há décadas — DB2, Oracle, PostgreSQL. Não é inércia: é que as garantias de integridade e transação são exatamente o que um saldo exige, e reescrever isso sobre uma base sem essas garantias significaria reimplementar tudo na aplicação, com muito mais chance de errar.

🐘

SQLite: o banco mais implantado do planeta

Existem mais instâncias de SQLite do que de todos os outros bancos somados: ele está em cada celular Android e iOS, em cada navegador, no Firefox, no Photoshop, em aviões e em carros. É um único arquivo, sem servidor, com transações ACID completas e integridade referencial — o modelo relacional inteiro em menos de um megabyte de código.

🔧

ORMs e o descompasso de impedância

Django ORM, SQLAlchemy, Prisma, Hibernate: todos existem para traduzir entre objetos (com herança, referências e grafos) e tabelas (com linhas e chaves). O descasamento entre esses dois modelos tem nome — object-relational impedance mismatch — e é a origem de armadilhas famosas como o problema N+1, em que um laço inocente sobre objetos dispara mil consultas.

← Trilha: Bancos de dados Próxima: SQL — consultando dados →