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.
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.
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.
O banco recusa operações que deixariam uma chave estrangeira apontando para o nada. É isso que impede seus dados de ficarem inconsistentes.
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
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.
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.
Teste sua intuição
NULL = NULL?
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.