📏 CAPÍTULO 10: NORMALIZAÇÃO DE DADOS (1FN, 2FN E 3FN)
🎯 Objetivos de Aprendizagem
Ao final deste capítulo (estimativa: 2 horas de estudo autoguiado), você será capaz de:
- 🔹 Compreender as anomalias graves de banco de dados (Anomalia de Inserção, Atualização e Exclusão) causadas por esquemas desnormalizados.
- 🔹 Dominar o conceito de Dependência Funcional Total e Transitiva.
- 🔹 Aplicar o processo de Normalização passo a passo: 1ª Forma Normal (Atomicidade), 2ª Forma Normal (Dependência Total) e 3ª Forma Normal (Sem Transitividade).
- 🔹 Refatorar bancos desorganizados para atingir o Padrão 3FN eliminando redundâncias.
A Normalização é a "vacina" contra dados ruins. É um processo passo a passo (algoritmo) que aplicamos nas nossas tabelas para eliminar redundâncias (dados repetidos à toa) e prevenir anomalias (erros que acontecem quando tentamos salvar ou apagar algo). 🛡️🧩
🏢 O Cenário Prático (Seu Desafio)
O setor comercial da TecProExpress criou uma planilha gigante para controlar Vendas. Nela, eles colocaram o Nome do Cliente, o Nome do Entregador, a Placa do Caminhão e o Valor do Frete — tudo na mesma linha! Quando o Entregador mudava de caminhão, o pessoal tinha que atualizar essa informação em 500 linhas diferentes manualmente, causando um caos no faturamento.
"Seu desafio é pegar esse 'Galinheiro de Dados' não normalizado e aplicar as três regras de ouro da Normalização para quebrá-lo em tabelas menores, perfeitamente integradas e imunes a anomalias."
🧠 Fundamentos: As Anomalias e a Dependência Funcional
Se não normalizarmos, o banco sofrerá três doenças fatais:
- Anomalia de Inserção: Você não consegue cadastrar um Caminhão novo no sistema porque ele ainda não fez nenhuma venda (e a tabela exige os dados da venda na mesma linha).
- Anomalia de Atualização: Você precisa alterar o endereço de um cliente em 500 registros antigos. Se o computador travar no registro 250, o banco ficará inconsistente.
- Anomalia de Exclusão: Se você deletar o registro da única venda que um cliente fez, você acidentalmente deleta o cadastro do cliente inteiro junto!
📊 O Algoritmo de Normalização
flowchart LR
UNF["❌ Tabela Caótica<br/>(A Planilha)"] --> FN1["✅ 1FN:<br/>Atomicidade"]
FN1 --> FN2["✅ 2FN:<br/>Sem Dependência Parcial"]
FN2 --> FN3["✅ 3FN:<br/>Sem Dependência Transitiva"]
🔍 Dependência Funcional: O que é isso?
Dizemos que B depende funcionalmente de A (ou A -> B) quando: se eu te der o valor de A, você consegue descobrir com certeza absoluta qual é o valor de B.
- Exemplo: O
CPFdetermina oNome(Um CPF só tem um dono). Mas oNomenão determina oCPF(Existem muitos "Joões"). A Chave Primária (PK) deve sempre ser o determinador de tudo na tabela!
📖 A Jornada das Formas Normais (DDL na Prática)
Vamos resolver o problema da TecProExpress passo a passo.
1️⃣ Primeira Forma Normal (1FN)
Regra: Todos os atributos devem ser atômicos. Sem "células duplas" ou grupos repetitivos.
- A Violação: O Excel tinha a coluna "Itens do Pedido" com
"Teclado, Mouse, Monitor"dentro da mesma célula. - A Correção (1FN): Quebramos isso. O Pedido vira 3 linhas diferentes.
2️⃣ Segunda Forma Normal (2FN)
Regra: Estar na 1FN + Nenhuma coluna pode depender apenas de metade da Chave Primária Composta.
- A Violação: Imagine que a Chave da nossa tabela seja
(ID_Venda, ID_Produto). A colunaNome_do_Produtosó depende do ID do Produto, ela não quer nem saber qual foi a Venda! Isso é dependência parcial. - A Correção (2FN): Puxamos o Produto para uma tabela separada.
3️⃣ Terceira Forma Normal (3FN)
Regra: Estar na 2FN + Nenhuma coluna "não-chave" pode depender de outra coluna "não-chave" (Dependência Transitiva).
- A Violação: Na tabela de Venda, temos o ID do Cliente, o Nome do Cliente e a Cidade do Cliente. A Cidade depende do Nome, que depende do ID. Se o Cliente não é a Chave Primária da tabela de Vendas, ele não pode morar lá com seus atributos.
- A Correção (3FN): Puxamos o Cliente para sua própria tabela.
🛠️ Prática Obrigatória: Quebrando o Monstro (DDL -> DML)
Cenário: O resultado da 3FN.
Após a sua análise, a planilha gigante virou três tabelas limpas: Cliente, Produto e a associativa Venda.
🚀 Script de Seed (Gabarito da 3FN)
-- PASSO 1: DDL (As Tabelas Bases - Fortes)
CREATE TABLE cliente_norm (
id_cliente INT PRIMARY KEY,
nome VARCHAR(100),
cidade VARCHAR(50)
);
CREATE TABLE produto_norm (
id_produto INT PRIMARY KEY,
descricao VARCHAR(100)
);
-- PASSO 2: DDL (A Tabela Normalizada que liga tudo sem redundância)
CREATE TABLE venda_norm (
id_venda INT PRIMARY KEY,
id_cliente INT, -- A FK apontando para o cliente (Nada de escrever a cidade dele aqui!)
id_produto INT,
FOREIGN KEY (id_cliente) REFERENCES cliente_norm(id_cliente),
FOREIGN KEY (id_produto) REFERENCES produto_norm(id_produto)
);
-- PASSO 3: DML (Carga dos Dados Sem Repetição)
INSERT INTO cliente_norm VALUES (1, 'Maria Silva', 'São Paulo');
INSERT INTO produto_norm VALUES (10, 'Teclado Gamer');
-- Agora registramos 100 vendas sem nunca digitar "São Paulo" de novo!
INSERT INTO venda_norm VALUES (1001, 1, 10);
🔍 Detalhamento do Código:
- Note que a tabela
venda_normé extremamente leve! Ela só guarda os números dos IDs. Se a Maria mudar de 'São Paulo' para 'Rio de Janeiro', você fará oUPDATEem exatamente 1 linha na tabelacliente_norm, e todas as milhares de vendas dela já estarão magicamente atualizadas! Isso é a força da 3FN.
💻 Ponte Prática: Do SQL Manual ao SQLAlchemy 2.0 ORM
Como o SQLAlchemy 2.0 combate as Anomalias de Inserção, Atualização e Exclusão através do modelo normalizado?
🔴 1. A Abordagem Manual (Tabela Monolítica Desnormalizada)
No modelo não-normalizado, para atualizar o endereço de um cliente, você é obrigado a rodar um UPDATE massivo em centenas de linhas de vendas:
# ❌ ABORDAGEM NÃO-NORMALIZADA: Redundância massiva
cursor.execute("UPDATE vendas_monoliticas SET cidade_cliente = 'Rio de Janeiro' WHERE nome_cliente = 'Maria Silva';")
# Se o banco tiver 500.000 vendas, esse UPDATE bloqueará a tabela inteira!
🟢 2. A Abordagem com SQLAlchemy 2.0 (Modelos 3FN Desacoplados)
Com os modelos normalizados em 3FN, alteramos o registro do cliente em exatamente uma linha da tabela clientes. Todas as relações e queries refletem a mudança instantaneamente:
# ✅ ABORDAGEM MODERNA EM 3FN COM SQLALCHEMY 2.0
from sqlalchemy import create_engine, String, Integer, ForeignKey, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship, Session
class Base(DeclarativeBase):
pass
class ClienteNormModel(Base):
"""3FN: Tabela isolada para a entidade Cliente."""
__tablename__ = "clientes_norm"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
nome: Mapped[str] = mapped_column(String(100), nullable=False)
cidade: Mapped[str] = mapped_column(String(50), nullable=False)
vendas: Mapped[list["VendaNormModel"]] = relationship("VendaNormModel", back_populates="cliente")
class ProdutoNormModel(Base):
"""3FN: Tabela isolada para a entidade Produto."""
__tablename__ = "produtos_norm"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
descricao: Mapped[str] = mapped_column(String(100), nullable=False)
class VendaNormModel(Base):
"""3FN: Tabela associativa contendo apenas identificadores e fatos transacionais."""
__tablename__ = "vendas_norm"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
id_cliente: Mapped[int] = mapped_column(ForeignKey("clientes_norm.id"), nullable=False)
id_produto: Mapped[int] = mapped_column(ForeignKey("produtos_norm.id"), nullable=False)
cliente: Mapped["ClienteNormModel"] = relationship("ClienteNormModel", back_populates="vendas")
produto: Mapped["ProdutoNormModel"] = relationship("ProdutoNormModel")
🛠️ Mini-Projeto 10 (BD): Refatoração de Dados Não-Normalizados (1FN a 3FN)
Objetivo: Demonstrar como uma atualização pontual no cliente normalizado atualiza todas as consultas de vendas sem anomalias de alteração.
📋 Pré-requisitos e Instalação
No terminal do seu ambiente virtual (PowerShell ou Bash), instale a biblioteca necessária:
pip install sqlalchemy
💻 Código Completo e Autocontido (miniprojeto_10_normalizacao.py)
Crie o arquivo miniprojeto_10_normalizacao.py e insira o código abaixo integralmente:
"""
Mini-Projeto 10: Refatoração de Dados Não-Normalizados (1FN a 3FN)
Curso: GTI - Banco de Dados Relacionais e Engenharia de Software
Stack: Python 3.11+ | SQLAlchemy 2.0 | SQLite
"""
import os
from sqlalchemy import create_engine, String, Integer, ForeignKey, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship, Session
# 1. Definição Declarativa Normalizada (3FN)
class Base(DeclarativeBase):
pass
class ClienteNormModel(Base):
"""3FN: Tabela isolada para a entidade Cliente."""
__tablename__ = "clientes_norm"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
nome: Mapped[str] = mapped_column(String(100), nullable=False)
cidade: Mapped[str] = mapped_column(String(50), nullable=False)
vendas: Mapped[list["VendaNormModel"]] = relationship("VendaNormModel", back_populates="cliente")
class ProdutoNormModel(Base):
"""3FN: Tabela isolada para a entidade Produto."""
__tablename__ = "produtos_norm"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
descricao: Mapped[str] = mapped_column(String(100), nullable=False)
class VendaNormModel(Base):
"""3FN: Tabela associativa contendo apenas identificadores e fatos transacionais."""
__tablename__ = "vendas_norm"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
id_cliente: Mapped[int] = mapped_column(ForeignKey("clientes_norm.id"), nullable=False)
id_produto: Mapped[int] = mapped_column(ForeignKey("produtos_norm.id"), nullable=False)
cliente: Mapped["ClienteNormModel"] = relationship("ClienteNormModel", back_populates="vendas")
produto: Mapped["ProdutoNormModel"] = relationship("ProdutoNormModel")
# 2. Ponto de Entrada Executável
if __name__ == "__main__":
DB_FILE = "tecpro_normalizado.db"
# Reset preventivo para garantir idempotência em testes repetidos
if os.path.exists(DB_FILE):
os.remove(DB_FILE)
engine = create_engine(f"sqlite:///{DB_FILE}", echo=False)
Base.metadata.create_all(bind=engine)
print("📐 NORMALIZAÇÃO DE DADOS (3FN) - TECPROEXPRESS")
print("=" * 65)
# 1. Povoando o banco normalizado
with Session(engine) as session:
cliente = ClienteNormModel(id=1, nome="Maria Silva", cidade="São Paulo")
prod1 = ProdutoNormModel(id=10, descricao="Teclado Mecânico")
prod2 = ProdutoNormModel(id=20, descricao="Monitor 4K")
session.merge(cliente)
session.merge(prod1)
session.merge(prod2)
session.flush()
venda1 = VendaNormModel(id_cliente=1, id_produto=10)
venda2 = VendaNormModel(id_cliente=1, id_produto=20)
session.add_all([venda1, venda2])
session.commit()
print("✅ Cliente, Produtos e 2 Vendas gravados em 3FN!")
# 2. Atualização Pontual (Zero Redundância):
with Session(engine) as session:
maria = session.get(ClienteNormModel, 1)
if maria:
print(f"\n🏠 Mudando cidade de Maria: {maria.cidade} -> Rio de Janeiro (Atualiza 1 único registro)")
maria.cidade = "Rio de Janeiro"
session.commit()
# 3. Consulta das vendas de Maria após a atualização:
print("\n🔍 Consultando histórico de vendas após o UPDATE pontual:")
with Session(engine) as session:
stmt = select(VendaNormModel).join(VendaNormModel.cliente).join(VendaNormModel.produto)
for v in session.scalars(stmt).all():
print(f" 📦 Venda #{v.id} | Cliente: {v.cliente.nome} ({v.cliente.cidade}) | Item: {v.produto.descricao}")
print("=" * 65)
🚀 Como Executar
Execute o script no terminal:
python miniprojeto_10_normalizacao.py
🖥️ Saída Esperada no Console
📐 NORMALIZAÇÃO DE DADOS (3FN) - TECPROEXPRESS
=================================================================
✅ Cliente, Produtos e 2 Vendas gravados em 3FN!
🏠 Mudando cidade de Maria: São Paulo -> Rio de Janeiro (Atualiza 1 único registro)
🔍 Consultando histórico de vendas após o UPDATE pontual:
📦 Venda #1 | Cliente: Maria Silva (Rio de Janeiro) | Item: Teclado Mecânico
📦 Venda #2 | Cliente: Maria Silva (Rio de Janeiro) | Item: Monitor 4K
=================================================================
💡 Checkpoint de Lógica
Reflexão Profissional: Um banco de dados 100% normalizado é sempre a melhor escolha? (Resposta: Para sistemas Transacionais/Operacionais, SIM! Mas em Data Warehouses de Analytics - onde a leitura de relatórios pesados é mais importante que a escrita - às vezes os arquitetos aplicam a Desnormalização propositalmente para evitar muitos cruzamentos de JOINs e acelerar a velocidade do painel). 🧠🛡️
🧪 Quiz de Fixação e Autoavaliação — Capítulo 10
🧪 Quiz de Fixação e Autoavaliação — Capítulo 10
1. O que exige a 1ª Forma Normal (1FN) em uma tabela relacional?
- A) Que todas as colunas sejam do tipo texto.
- B) Que todos os atributos sejam atômicos (indivisíveis), sem repetição de grupos de dados ou colunas com múltiplos valores na mesma célula (ex: 'Telefone 1, Telefone 2' ou listas separadas por vírgula).
- C) Que a tabela tenha exatamente 3 chaves estrangeiras.
- D) Que o banco use criptografia quântica.
💡 Ver Resposta e Justificativa
Resposta Correta: B
Justificativa: A 1FN elimina arrays e campos compostos: cada célula deve guardar exatamente um único valor atômico.
2. Quando dizemos que uma tabela que já está na 1FN atingiu a 2ª Forma Normal (2FN)?
- A) Quando todos os atributos não-chave dependem TOTALMENTE da Chave Primária por inteiro, eliminando dependências parciais em tabelas com chaves compostas.
- B) Quando a tabela tem mais de 2 anos de uso.
- C) Quando o banco é migrado para PostgreSQL.
- D) Quando todos os valores nulos são apagados.
💡 Ver Resposta e Justificativa
Resposta Correta: A
Justificativa: A 2FN combate dependências parciais em tabelas com PKs compostas: nenhum atributo pode depender de apenas 'metade' da chave primária.
3. O que proíbe a 3ª Forma Normal (3FN) para eliminar redundâncias e inconsistências de dados?
- A) Proíbe o uso de comandos DELETE.
- B) Proíbe a existência de Dependências Transitivas (atributos não-chave que dependem de outros atributos não-chave em vez de dependerem diretamente da Chave Primária).
- C) Proíbe o uso de índices B-Tree.
- D) Obriga todas as tabelas a terem nomes em inglês.
💡 Ver Resposta e Justificativa
Resposta Correta: B
Justificativa: Regra da 3FN: 'Todo atributo deve depender da chave, de toda a chave e de nada além da chave'. Se cidade depende de cep, eles devem ir para uma tabela separada de Endereços.
Coloque este conhecimento em prática agora mesmo executando o roteiro autoguiado:
👉 ATIVIDADE 04: NORMALIZAÇÃO