Normalização em Bancos de Dados Relacionais
Uma introdução ao conceito de normalização em bancos de dados relacionais e suas formas normais.
Table of Contents
Introdução
Todo desenvolvedor já viu: aquela tabela com 50 colunas onde metade dos dados se repete em cada row, ou aquele update que precisa rodar em 5 lugares diferentes porque “o mesmo dado mora em várias tabelas”. Normalização resolve esses problemas de forma sistemática, mas introduz outros: queries mais complexas, mais joins, schemas menos intuitivos para quem não conhece o domínio. Este post cobre as Formas normais, o processo de normalization, e quando os benefícios compensam os custos.
O que é normalização em bancos de dados
Normalização é um dos conceitos mais fundamentais e frequentemente mal compreendidos em database design. Trata-se do processo sistemático de organizar dados em um banco relacional para reduzir redundância e melhorar integridade, através da decomposição de tabelas em estruturas menores e bem definidas, estabelecendo relacionamentos claros entre elas. O objetivo central é eliminar grupos repetidos de dados e garantir que cada elemento seja armazenado de forma lógica e consistente. Porém, é crucial entender que a normalização não é um fim absoluto. Em determinados contextos, a desnormalização consciente pode ser necessária para atender requisitos específicos de performance.
Formas normais
O processo de normalização é estruturado em níveis progressivos, conhecidos como formas normais. Cada nível adiciona restrições que aumentam a integridade dos dados.
1NF
Uma tabela está em 1NF quando satisfaz três requisitos:
- Primary key definida: Cada registro deve ser unicamente identificável
- Atomicidade de valores: Cada campo contém apenas um valor, não múltiplos valores ou grupos repetidos
- Homogeneidade de tipos: Valores em cada coluna devem ser do mesmo tipo de dado
Exemplo de violação de 1NF:
-- Tabela NÃO está em 1NF
CREATE TABLE pedidos (
pedido_id INT PRIMARY KEY,
cliente VARCHAR(100),
telefones VARCHAR(200) -- "11-98765-4321, 11-3456-7890"
);
-- Correção para 1NF
CREATE TABLE pedidos (
pedido_id INT PRIMARY KEY,
cliente VARCHAR(100)
);
CREATE TABLE pedidos_telefones (
pedido_id INT REFERENCES pedidos(pedido_id),
telefone VARCHAR(20),
PRIMARY KEY (pedido_id, telefone)
);
2NF
Uma tabela está em 2NF quando:
- Está em 1NF
- Todo atributo não-chave depende completamente da primary key
- Não existem dependências parciais (atributos dependendo apenas de parte de uma chave composta)
Exemplo de violação de 2NF:
-- Chave composta: (employee_id, project_id)
CREATE TABLE employee_projects (
employee_id INT,
project_id INT,
employee_name VARCHAR(100), -- Depende APENAS de employee_id
job_code VARCHAR(10), -- Depende APENAS de employee_id
project_name VARCHAR(100), -- Depende APENAS de project_id
hours_allocated INT, -- Depende da chave completa
PRIMARY KEY (employee_id, project_id)
);
-- Correção para 2NF
CREATE TABLE employees (
employee_id INT PRIMARY KEY,
employee_name VARCHAR(100),
job_code VARCHAR(10)
);
CREATE TABLE projects (
project_id INT PRIMARY KEY,
project_name VARCHAR(100)
);
CREATE TABLE employee_projects (
employee_id INT REFERENCES employees(employee_id),
project_id INT REFERENCES projects(project_id),
hours_allocated INT,
PRIMARY KEY (employee_id, project_id)
);
3NF
Uma tabela está em 3NF quando:
- Está em 2NF
- Todo atributo não-chave depende diretamente da primary key
- Não existem dependências transitivas (atributos dependendo de outros atributos não-chave)
-- Chave composta: (employee_id, project_id)
CREATE TABLE employees (
employee_id INT PRIMARY KEY,
employee_name VARCHAR(100),
state_code CHAR(2),
state_name VARCHAR(50) -- state_name depende de state_code, não de employee_id
);
-- Correção para 3NF
CREATE TABLE employees (
employee_id INT PRIMARY KEY,
employee_name VARCHAR(100),
job_code VARCHAR(10)
);
CREATE TABLE projects (
project_id INT PRIMARY KEY,
project_name VARCHAR(100)
);
CREATE TABLE employee_projects (
employee_id INT REFERENCES employees(employee_id),
project_id INT REFERENCES projects(project_id),
hours_allocated INT,
PRIMARY KEY (employee_id, project_id)
);
Boyce-Codd Normal Form (BCNF)
Uma tabela está em BCNF quando:
- Está em 3NF
- Todo determinante (conjunto de atributos que identifica unicamente uma linha) é uma chave candidata
BCNF é uma versão mais restritiva de 3NF que lida com casos específicos de dependências onde 3NF ainda permite anomalias. Na prática, muitos schemas em 3NF já satisfazem BCNF.
O Processo de Normalização
- Identificar a Estrutura Inicial: Começar com a tabela em forma não-normalizada, geralmente refletindo como os dados são coletados ou apresentados ao usuário.
- Aplicar 1NF: Eliminar grupos repetidos e garantir atomicidade.
- Aplicar 2NF: Identificar e eliminar dependências parciais. Atributos que dependem apenas de parte da primary key devem ser movidos para tabelas separadas.
- Aplicar 3NF: Identificar e eliminar dependências transitivas. Atributos que dependem de outros atributos não-chave devem ser extraídos para suas próprias tabelas.
- Validar BCNF (Opcional): Verificar se todos os determinantes são chaves candidatas.
Benefícios da Normalização
- Redução de Redundância: Cada fato é armazenado uma única vez, eliminando duplicação desnecessária de dados. Isso economiza espaço de armazenamento e reduz inconsistências.
- Integridade Aprimorada: Com dados não-redundantes, atualizações afetam apenas um local. Isso elimina anomalias de atualização onde o mesmo dado poderia ser alterado em alguns lugares mas não em outros.
- Manutenção Simplificada: Schemas normalizados são mais fáceis de entender e modificar. Adicionar novos atributos ou entidades não requer alterações em múltiplas tabelas.
- Queries e Reporting Facilitados: Relacionamentos bem definidos através de foreign keys tornam joins mais intuitivos e previnem combinações inválidas de dados.
- Performance Potencialmente Melhor: Embora a normalização possa aumentar o número de joins, ela também reduz o tamanho das tabelas individuais, melhorando a eficiência de cache e reduzindo I/O em muitos cenários.
Quando Desnormalizar
Normalização até 3NF é geralmente o objetivo ideal, mas existem cenários legítimos para desnormalização:
- Data Warehouses e OLAP: Schemas dimensionais (star/snowflake) intencionalmente desnormalizam para otimizar queries analíticas complexas.
- Workloads de Leitura Intensiva: Quando queries específicas são executadas milhares de vezes por segundo, duplicar dados pode eliminar joins custosos.
- Caching de Valores Computados: Armazenar valores agregados (totais, médias) que seriam caros de calcular repetidamente.