🧩 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 CASCADE na 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 rodarmos DELETE 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.

  1. Crie a tabela genérica veiculo (Pai).
  2. Crie as tabelas filhas caminhao e moto.
  3. 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

Importante

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

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_gerente aponta para Funcionario.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.


🎯 Laboratório Prático

Coloque este conhecimento em prática agora mesmo executando o roteiro autoguiado:
👉 ATIVIDADE 02: MODELAGEM CONCEITUAL