🧩 CAPÍTULO 08: EXTENSÕES DO MER E REVISÃO
🎯 Objetivos de Aprendizagem
Ao final deste capítulo (estimativa: 2 horas de estudo autoguiado), você será capaz de:
- 🔹 Modelar estruturas avançadas no MER: Especialização/Generalização (Herança de Tabelas), Entidades Fracas e Entidades Associativas.
- 🔹 Dominar relacionamentos recursivos (Autorrelacionamento) como árvores de categorias e hierarquias de chefia.
- 🔹 Mapear herança de classes para o banco relacional (Estratégia Joined Table, Single Table e Table per Class).
- 🔹 Implementar Entidade Fraca e Herança de Tabelas (Joined Table Inheritance) no SQLAlchemy 2.0. (O código de autorrelacionamento com
remote_sideé aprofundado no laboratório prático da disciplina.)
Até agora, lidamos com objetos simples (Clientes, Produtos, Pedidos). Mas a vida real não é apenas "Pai e Filho". Como mapeamos um "Dependente" que só existe se o titular existir? Como mapeamos a herança de características? Hoje vamos ver as Extensões do MER. 🛡️🧩
🏢 O Cenário Prático (Seu Desafio)
O RH da TecProExpress solicitou que o banco de dados armazene os Dependentes (filhos) de cada funcionário para o plano de saúde. Além disso, a empresa agora possui uma frota mista: Caminhões (que têm capacidade de carga) e Motos (que têm cilindrada), mas ambos são Veículos.
"Seu desafio é elevar o nível da sua arquitetura, aplicando os conceitos de 'Entidade Fraca' para os dependentes e 'Herança' para a frota, garantindo que o banco de dados seja escalável e evite colunas com valores NULL desnecessários."
🧠 Fundamentos: Modelagem Avançada
1. Entidade Fraca (Dependência Existencial)
Uma Entidade Fraca não tem vida própria. O "Filho do João" só está no plano de saúde porque o João é funcionário. Se João for demitido (deletado), o dependente deve desaparecer do banco também.
- No SQL, garantimos isso usando a restrição
ON DELETE CASCADEna Chave Estrangeira.
2. Generalização e Especialização (Herança)
Assim como na Programação Orientada a Objetos (POO), no banco de dados podemos ter uma tabela "Pai" Genérica e tabelas "Filhas" Específicas.
📊 Diagrama de Herança (IS-A)
flowchart TD
VEI["🚙 VEÍCULO <br/> ID, Placa, Ano"] -->|Especialização| CAM["🚛 CAMINHÃO <br/> Cap_Carga"]
VEI -->|Especialização| MOT["🏍️ MOTO <br/> Cilindradas"]
style VEI fill:#e3f2fd
style CAM fill:#fffde7
style MOT fill:#fffde7
- Vantagem: Evita colocar "Capacidade_Carga" e "Cilindradas" na mesma tabela, o que faria as Motos terem a coluna de carga como
NULL(desperdício de espaço e lógica).
📖 Exemplo Guiado: Criando uma Entidade Fraca (DDL -> DML)
Veja como modelar a dependência forte no PostgreSQL ou MySQL para a TecProExpress.
🛠️ Código do Exemplo
-- PASSO 1: DDL (A Entidade Forte / Titular)
CREATE TABLE funcionario (
id INT PRIMARY KEY,
nome VARCHAR(100)
);
-- PASSO 2: DDL (A Entidade Fraca / Dependente)
CREATE TABLE dependente (
id_func INT,
nome_dep VARCHAR(100),
data_nasc DATE,
PRIMARY KEY (id_func, nome_dep), -- Chave Composta!
CONSTRAINT fk_titular FOREIGN KEY (id_func)
REFERENCES funcionario(id)
ON DELETE CASCADE -- A mágica acontece aqui!
);
-- PASSO 3: DML (Carga Inicial)
INSERT INTO funcionario VALUES (10, 'Carlos Oliveira');
INSERT INTO dependente VALUES (10, 'Pedrinho', '2015-05-10');
🔍 Detalhamento do Código:
- Chave Composta: A PK do dependente é a junção do ID do funcionário + o nome do dependente.
ON DELETE CASCADE: Se rodarmosDELETE FROM funcionario WHERE id = 10;, o banco de dados, sozinho, vai deletar o "Pedrinho" da tabela dependente. Integridade automática!
🛠️ Prática Obrigatória: Implementando a Frota (Herança)
Cenário: A frota mista da TecProExpress.
- Crie a tabela genérica
veiculo(Pai). - Crie as tabelas filhas
caminhaoemoto. - Faça com que a Chave Primária das filhas também seja a Chave Estrangeira que aponta para o Pai.
🚀 Script de Seed (Gabarito Físico)
-- DDL Genérico (Pai)
CREATE TABLE veiculo (
id INT PRIMARY KEY,
placa VARCHAR(7) UNIQUE,
ano INT
);
-- DDL Específico (Caminhão)
CREATE TABLE caminhao (
id_veiculo INT PRIMARY KEY, -- PK e FK ao mesmo tempo!
capacidade_toneladas INT,
FOREIGN KEY (id_veiculo) REFERENCES veiculo(id)
);
-- DML (Inserindo a Herança)
-- Passo A: Insere na tabela Pai
INSERT INTO veiculo VALUES (1, 'AAA1234', 2026);
-- Passo B: Complementa na tabela Filha
INSERT INTO caminhao VALUES (1, 15);
🔍 O Segredo da Abstração
No DML acima, o veículo ID 1 existe em duas partes: os dados gerais estão no Pai, e os dados específicos estão no Caminhão. Na hora do relatório, usaremos um JOIN para juntar as duas metades.
💻 Ponte Prática: Do SQL Manual ao SQLAlchemy 2.0 ORM
Como o SQLAlchemy 2.0 mapeia a Herança de Classes Python para tabelas relacionais especializadas (Joined Table Inheritance)?
🔴 1. A Abordagem Manual (Dois Inserts e Queries Manuais com JOIN)
No SQL manual, para salvar um caminhão, o programador é obrigado a rodar dois comandos INSERT separados e cuidar da sincronização das PKs:
# ❌ ABORDAGEM COM SQL MANUAL: 2 INSERTs manuais suscetíveis a inconsistências
cursor.execute("INSERT INTO veiculo (id, placa, ano) VALUES (1, 'AAA1234', 2026);")
cursor.execute("INSERT INTO caminhao (id_veiculo, capacidade_ton) VALUES (1, 15);")
🟢 2. A Abordagem com SQLAlchemy 2.0 (Herança de Tabelas Unidas - Joined Table Inheritance)
Com o SQLAlchemy 2.0, a classe CaminhaoModel herda de VeiculoModel. O ORM cuida de dividir os dados entre as tabelas e unificá-los na consulta:
# ✅ ABORDAGEM MODERNA: Herança de Classes Mapeada para Tabelas
from sqlalchemy import create_engine, String, Integer, ForeignKey, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session
class Base(DeclarativeBase):
pass
class VeiculoModel(Base):
"""Tabela Pai (Generalização)."""
__tablename__ = "veiculos_geral"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
placa: Mapped[str] = mapped_column(String(7), unique=True, nullable=False)
tipo: Mapped[str] = mapped_column(String(20)) # Discriminador polimórfico
__mapper_args__ = {
"polymorphic_on": "tipo",
"polymorphic_identity": "veiculo_generico"
}
class CaminhaoModel(VeiculoModel):
"""Tabela Filha Especializada (Herança)."""
__tablename__ = "caminhoes_especificos"
id: Mapped[int] = mapped_column(ForeignKey("veiculos_geral.id"), primary_key=True)
capacidade_ton: Mapped[int] = mapped_column(Integer, nullable=False)
__mapper_args__ = {
"polymorphic_identity": "caminhao"
}
class MotoModel(VeiculoModel):
"""Tabela Filha Especializada (Herança)."""
__tablename__ = "motos_especificas"
id: Mapped[int] = mapped_column(ForeignKey("veiculos_geral.id"), primary_key=True)
cilindradas: Mapped[int] = mapped_column(Integer, nullable=False)
__mapper_args__ = {
"polymorphic_identity": "moto"
}
🛠️ Mini-Projeto 08 (BD): Herança Polimórfica de Frota em Python
Objetivo: Persistir e consultar uma frota mista (Caminhões e Motos) usando herança nativa (Joined Table Inheritance) do SQLAlchemy 2.0.
📋 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_08_heranca.py)
Crie o arquivo miniprojeto_08_heranca.py e insira o código abaixo integralmente:
"""
Mini-Projeto 08: Herança Polimórfica de Frota em Python
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, Session
# 1. Definição Declarativa com Herança de Tabelas Conectadas (Joined Table Inheritance)
class Base(DeclarativeBase):
pass
class VeiculoModel(Base):
"""Tabela Pai (Generalização)."""
__tablename__ = "veiculos_geral"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
placa: Mapped[str] = mapped_column(String(7), unique=True, nullable=False)
tipo: Mapped[str] = mapped_column(String(20)) # Discriminador polimórfico
__mapper_args__ = {
"polymorphic_on": "tipo",
"polymorphic_identity": "veiculo_generico",
}
class CaminhaoModel(VeiculoModel):
"""Tabela Filha Especializada (Herança)."""
__tablename__ = "caminhoes_especificos"
id: Mapped[int] = mapped_column(ForeignKey("veiculos_geral.id"), primary_key=True)
capacidade_ton: Mapped[int] = mapped_column(Integer, nullable=False)
__mapper_args__ = {
"polymorphic_identity": "caminhao",
}
class MotoModel(VeiculoModel):
"""Tabela Filha Especializada (Herança)."""
__tablename__ = "motos_especificas"
id: Mapped[int] = mapped_column(ForeignKey("veiculos_geral.id"), primary_key=True)
cilindradas: Mapped[int] = mapped_column(Integer, nullable=False)
__mapper_args__ = {
"polymorphic_identity": "moto",
}
# 2. Ponto de Entrada Executável
if __name__ == "__main__":
DB_FILE = "tecpro_frota_polimorfica.db"
# Reset preventivo: indispensável devido à restrição UNIQUE na coluna 'placa'
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("🧭 HERANÇA RELACIONAL (JOINED TABLE INHERITANCE) - TECPROEXPRESS")
print("=" * 65)
# 1. Inserindo objetos polimórficos diretamente
with Session(engine) as session:
c1 = CaminhaoModel(placa="VOL4499", capacidade_ton=28)
m1 = MotoModel(placa="HON1234", cilindradas=250)
session.add_all([c1, m1])
session.commit()
print("✅ Caminhão e Moto persistidos com herança automática nas tabelas!")
# 2. Consultando todos os veículos como a classe Pai
print("\n🔍 Consultando a classe genérica VeiculoModel (Polimorfismo puro):")
with Session(engine) as session:
veiculos = session.scalars(select(VeiculoModel)).all()
for v in veiculos:
if isinstance(v, CaminhaoModel):
print(f" 🚛 Caminhão [Placa: {v.placa}] -> Carga: {v.capacidade_ton} toneladas")
elif isinstance(v, MotoModel):
print(f" 🏍️ Moto [Placa: {v.placa}] -> Potência: {v.cilindradas} cc")
print("=" * 65)
🚀 Como Executar
Execute o script no terminal:
python miniprojeto_08_heranca.py
🖥️ Saída Esperada no Console
🧭 HERANÇA RELACIONAL (JOINED TABLE INHERITANCE) - TECPROEXPRESS
=================================================================
✅ Caminhão e Moto persistidos com herança automática nas tabelas!
🔍 Consultando a classe genérica VeiculoModel (Polimorfismo puro):
🚛 Caminhão [Placa: VOL4499] -> Carga: 28 toneladas
🏍️ Moto [Placa: HON1234] -> Potência: 250 cc
=================================================================
💡 Checkpoint de Lógica
Revisão Estratégica: Chegamos ao fim da modelagem pura. Você aprendeu a criar Entidades, ligar Relacionamentos (1:N, N:M), lidar com Dependências e Herança. O próximo passo será refinar tudo isso com a "Normalização", a vacina contra as redundâncias. O seu raciocínio estrutural está preparado? 🧠🛡️
🧪 Quiz de Fixação e Autoavaliação — Capítulo 08
🧪 Quiz de Fixação e Autoavaliação — Capítulo 08
1. O que caracteriza uma 'Entidade Fraca' no Modelo Entidade-Relacionamento?
- A) Uma tabela com poucos registros cadastrados.
- B) Uma entidade cuja existência depende de outra entidade proprietária e cuja chave primária é composta pela sua chave parcial somada à chave estrangeira da entidade pai (ex: Dependente de um Funcionário).
- C) Uma tabela que não aceita índices.
- D) Um banco de dados corrompido.
💡 Ver Resposta e Justificativa
Resposta Correta: B
Justificativa: Entidades fracas não possuem identidade independente no mundo real: se o 'Funcionário' for desligado da empresa, seus 'Dependentes' deixam de fazer sentido no sistema.
2. O que é um 'Autorrelacionamento' (Relacionamento Recursivo)?
-
A) Quando uma tabela possui uma chave estrangeira que aponta para a chave primária da PRÓPRIA tabela (ex:
Funcionario.id_gerenteaponta paraFuncionario.id_funcionario). - B) Quando duas tabelas têm o mesmo nome.
- C) Quando o banco se conecta com a internet sozinho.
- D) Quando um loop infinito trava o servidor.
💡 Ver Resposta e Justificativa
Resposta Correta: A
Justificativa: Autorrelacionamentos são clássicos para representar hierarquias (ex: chefia de funcionários, árvores de categorias/subcategorias e genealogia animal no CattleFlow).
3. Na estratégia 'Single Table Inheritance' (Tabela Única) de mapeamento de herança, como o SGBD diferencia se a linha é do tipo PessoaFisica ou PessoaJuridica?
- A) Criando dois bancos de dados separados.
-
B) Utilizando uma coluna discriminadora (ex:
tipo_pessoa VARCHAR(2)), deixando nulos os atributos que não pertencem àquela subclasse específica. - C) Calculando a raiz quadrada do ID.
- D) Através do endereço IP do cliente.
💡 Ver Resposta e Justificativa
Resposta Correta: B
Justificativa: Single Table coloca todas as subclasses na mesma tabela com uma coluna 'discriminator', sendo muito rápida para consultas polimórficas sem necessidade de JOINs.
Coloque este conhecimento em prática agora mesmo executando o roteiro autoguiado:
👉 ATIVIDADE 02: MODELAGEM CONCEITUAL