Modelagem ER Avançada: relacionamentos, entidades fracas e generalização

No módulo anterior nós construímos as fundações do Modelo Entidade-Relacionamento: entidades, atributos, chaves e a noção básica de associação. Agora vou te conduzir por um terreno mais sofisticado, aquele em que os modelos deixam de ser desenhos ingênuos e passam a refletir com fidelidade a complexidade do mundo real. Eu insisto neste módulo porque é exatamente aqui que as provas costumam separar quem decorou definições de quem realmente entendeu modelagem. Questões sobre cardinalidade, participação total ou parcial, entidades fracas e generalização aparecem com enorme frequência, e quase sempre vêm disfarçadas de um enunciado prático em que você precisa decidir qual notação aplicar. Então vou te mostrar cada conceito com diagramas, exemplos e a tradução para SQL, porque é nessa travessia do conceitual para o físico que tudo finalmente faz sentido.

Tipos de relacionamentos por cardinalidade

A cardinalidade de um relacionamento descreve quantas instâncias de uma entidade podem se associar a instâncias de outra. Existem três configurações que você precisa dominar de cor, porque elas governam tudo o que vem depois, inclusive como o relacionamento será materializado em tabelas e chaves estrangeiras.

O relacionamento um-para-um (1:1) ocorre quando cada instância de um lado se liga a no máximo uma instância do outro. O exemplo clássico é país e capital: um país tem uma capital, e aquela capital pertence a exatamente um país. Repare que esse tipo de relacionamento é relativamente raro, e quando aparece muitas vezes sinaliza que as duas entidades poderiam, talvez, ser uma só, a menos que haja boa razão para separá-las, como segurança, desempenho ou opcionalidade.

O relacionamento um-para-muitos (1:N) é o mais comum de todos. Uma instância do lado “um” se associa a várias do lado “muitos”, mas cada instância do lado “muitos” se liga a apenas uma do lado “um”. Pense num departamento que tem vários funcionários, enquanto cada funcionário pertence a um único departamento. Esse padrão se traduz diretamente numa chave estrangeira na tabela do lado “muitos”.

O relacionamento muitos-para-muitos (N:M) acontece quando ambos os lados podem se associar a várias instâncias do outro. Um aluno se matricula em vários cursos, e um curso recebe vários alunos. Esse tipo nunca pode ser representado por uma única chave estrangeira; ele sempre exige uma tabela intermediária, que estudaremos em detalhe quando falarmos de entidades associativas.

erDiagram
    PAIS ||--|| CAPITAL : possui
    DEPARTAMENTO ||--o{ FUNCIONARIO : emprega
    ALUNO }o--o{ CURSO : matricula

Definição. A cardinalidade máxima indica quantas instâncias do outro lado uma instância pode se relacionar, expressa como 1 ou N. A cardinalidade mínima indica quantas instâncias do outro lado uma instância precisa obrigatoriamente se relacionar, sendo 0 quando opcional e 1 quando obrigatória. Toda restrição de cardinalidade é, na verdade, um par mínimo-máximo.

Cardinalidade e restrições de participação

Aqui vou te pedir atenção redobrada, porque muita gente confunde os dois eixos. A cardinalidade máxima responde “quantos no máximo”, e a cardinalidade mínima, também chamada de participação, responde “é obrigatório ou opcional”. São perguntas independentes, e juntas formam a notação de pé de galinha que você vê nos diagramas modernos.

Quando a participação de uma entidade é total (ou obrigatória), toda instância dela precisa participar do relacionamento. Se cada funcionário precisa obrigatoriamente pertencer a um departamento, então a participação de Funcionário no relacionamento “pertence” é total. Na notação tradicional de Chen, isso aparece como uma linha dupla ligando a entidade ao losango. Quando a participação é parcial (ou opcional), nem toda instância precisa participar. Um departamento pode existir sem nenhum funcionário ainda alocado, então a participação de Departamento é parcial, desenhada com linha simples.

Vou cruzar os dois eixos numa tabela, porque visualizar a combinação ajuda muito na hora da prova.

Lado Cardinalidade máxima Participação (mínima) Leitura
Funcionário em “pertence” 1 departamento total (1) todo funcionário tem exatamente um departamento
Departamento em “pertence” N funcionários parcial (0) um departamento pode ter zero ou muitos funcionários
Funcionário em “gerencia” 1 departamento parcial (0) nem todo funcionário gerencia algo
Departamento em “gerencia” 1 funcionário total (1) todo departamento tem um gerente obrigatório

Repare como o relacionamento “gerencia” e o “pertence” coexistem entre as mesmas duas entidades com semânticas completamente distintas. Esse é um ponto que adoram cobrar: a participação é uma propriedade de cada lado dentro de cada relacionamento específico, não uma propriedade fixa da entidade.

erDiagram
    DEPARTAMENTO ||--o{ FUNCIONARIO : pertence
    DEPARTAMENTO ||--|| FUNCIONARIO : gerencia

Quando traduzimos participação total para o relacional, ela vira uma restrição NOT NULL na chave estrangeira. Veja como o “pertence” obrigatório do funcionário e o gerente obrigatório do departamento se expressam.

CREATE TABLE departamento (
    id_departamento  SERIAL PRIMARY KEY,
    nome             VARCHAR(80) NOT NULL,
    id_gerente       INTEGER NOT NULL  -- participação total: todo depto tem gerente
);

CREATE TABLE funcionario (
    id_funcionario   SERIAL PRIMARY KEY,
    nome             VARCHAR(120) NOT NULL,
    id_departamento  INTEGER NOT NULL  -- participação total: todo func. tem depto
        REFERENCES departamento(id_departamento)
);

ALTER TABLE departamento
    ADD CONSTRAINT fk_gerente
    FOREIGN KEY (id_gerente) REFERENCES funcionario(id_funcionario);

Atenção. Quando uma chave estrangeira pode ser nula, a participação é parcial; quando é NOT NULL, a participação é total. Mas existe uma armadilha: se Funcionário referencia Departamento e Departamento referencia o gerente Funcionário, ambos com NOT NULL, você cria uma dependência circular que impede inserir a primeira linha. Resolve-se com inserção em transação adiando a verificação, ou tornando um dos lados temporariamente anulável.

Grau do relacionamento: binários, ternários e n-ários

Além da cardinalidade, classificamos relacionamentos pelo grau, que é o número de entidades participantes. O grau binário, com duas entidades, domina a esmagadora maioria dos modelos. Mas há situações em que dois não bastam.

Um relacionamento ternário envolve três entidades simultaneamente, e o ponto decisivo é que a associação só faz sentido quando as três aparecem juntas. O exemplo canônico é “fornecimento”: um Fornecedor fornece uma Peça para um Projeto. Note que não dá para decompor isso em três relacionamentos binários sem perder informação, porque saber que o fornecedor A fornece a peça X, e que a peça X vai para o projeto P, e que o fornecedor A atende o projeto P, não nos diz se o fornecedor A forneceu especificamente a peça X para o projeto P. A semântica da tripla é irredutível.

erDiagram
    FORNECEDOR }o--o{ FORNECIMENTO : participa
    PECA }o--o{ FORNECIMENTO : participa
    PROJETO }o--o{ FORNECIMENTO : participa

Um relacionamento n-ário generaliza para n entidades com n maior que três, como uma prescrição que liga Médico, Paciente, Medicamento e Farmácia. Esses são raros e quase sempre difíceis de validar, então a recomendação prática é representá-los por uma entidade associativa central que se conecta a cada participante por um relacionamento binário. Isso transforma o losango n-ário num retângulo que carrega as chaves estrangeiras de todos os participantes.

-- Relacionamento ternário materializado como tabela associativa
CREATE TABLE fornecimento (
    id_fornecedor  INTEGER REFERENCES fornecedor(id_fornecedor),
    id_peca        INTEGER REFERENCES peca(id_peca),
    id_projeto     INTEGER REFERENCES projeto(id_projeto),
    quantidade     INTEGER NOT NULL CHECK (quantidade > 0),
    PRIMARY KEY (id_fornecedor, id_peca, id_projeto)
);

Existe ainda o relacionamento recursivo, ou auto-relacionamento, em que uma entidade se relaciona consigo mesma. O caso típico é a hierarquia de funcionários, em que um funcionário é gerente de outros funcionários. No diagrama, a entidade se liga a ela própria; no relacional, surge uma chave estrangeira que aponta para a própria tabela.

erDiagram
    FUNCIONARIO ||--o{ FUNCIONARIO : gerencia

CREATE TABLE empregado (
    id_empregado  SERIAL PRIMARY KEY,
    nome          VARCHAR(120) NOT NULL,
    id_gerente    INTEGER REFERENCES empregado(id_empregado)  -- nulo na raiz
);

Repare que id_gerente é anulável: o presidente da empresa não tem gerente, então sua participação no auto-relacionamento é parcial. É um detalhe pequeno, mas mostra como participação e recursão se combinam.

Atributos de relacionamento e entidades associativas

Nem todo atributo pertence a uma entidade. Alguns nascem da própria associação e não fazem sentido fora dela. Pense no relacionamento N:M “matrícula” entre Aluno e Curso: a nota final e a data da matrícula não são propriedade do aluno, nem do curso, mas exatamente da combinação dos dois. Esses são atributos de relacionamento.

Quando um relacionamento N:M carrega atributos próprios, nós o promovemos a uma entidade associativa, um retângulo que materializa o losango. Essa é a regra de ouro da tradução do conceitual para o relacional: todo N:M, com ou sem atributos, vira uma tabela intermediária cuja chave primária é a combinação das chaves estrangeiras das entidades participantes. Se houver atributos próprios, eles entram como colunas dessa tabela.

erDiagram
    ALUNO ||--o{ MATRICULA : realiza
    CURSO ||--o{ MATRICULA : recebe
    MATRICULA {
        date data_matricula
        numeric nota_final
    }

CREATE TABLE matricula (
    id_aluno        INTEGER REFERENCES aluno(id_aluno),
    id_curso        INTEGER REFERENCES curso(id_curso),
    data_matricula  DATE NOT NULL DEFAULT CURRENT_DATE,
    nota_final      NUMERIC(4,2) CHECK (nota_final BETWEEN 0 AND 10),
    PRIMARY KEY (id_aluno, id_curso)
);

Regra prática de tradução. Relacionamento 1:N coloca a chave estrangeira no lado “muitos”. Relacionamento 1:1 coloca a chave estrangeira em qualquer um dos lados, de preferência no de participação total. Relacionamento N:M sempre gera uma nova tabela. Atributos do relacionamento acompanham a chave estrangeira no 1:N, ou entram na tabela associativa no N:M.

Entidades fracas em detalhe

Chegamos a um dos conceitos mais elegantes e mais cobrados do modelo ER. Uma entidade fraca é aquela que não possui atributos suficientes para formar sozinha uma chave primária. Ela não tem existência independente: depende de outra entidade, chamada entidade forte ou identificadora, tanto para existir quanto para ser identificada de forma única.

Pense num dependente de funcionário para fins de plano de saúde. O nome do dependente, por si só, não identifica nada de forma global, porque pode haver vários “Maria” entre os dependentes de funcionários diferentes. Mas dentro do contexto de um funcionário específico, o nome do dependente é único. Esse nome é o que chamamos de identificador parcial (ou chave parcial, ou discriminador): ele distingue as instâncias da entidade fraca apenas dentro do escopo da entidade forte à qual ela se liga.

A ligação entre a entidade fraca e sua entidade forte se dá por um relacionamento identificador, e ele tem duas propriedades inegociáveis. Primeiro, a participação da entidade fraca nesse relacionamento é sempre total, porque um dependente não pode existir sem o funcionário do qual depende. Segundo, a chave primária completa da entidade fraca é a combinação do identificador parcial dela com a chave primária da entidade forte. Na notação de Chen, a entidade fraca aparece com retângulo de borda dupla e o relacionamento identificador com losango de borda dupla.

erDiagram
    FUNCIONARIO ||--o{ DEPENDENTE : possui
    FUNCIONARIO {
        int cpf PK
        string nome
    }
    DEPENDENTE {
        string nome "identificador parcial"
        date data_nascimento
        string parentesco
    }

CREATE TABLE funcionario (
    cpf   CHAR(11) PRIMARY KEY,
    nome  VARCHAR(120) NOT NULL
);

CREATE TABLE dependente (
    cpf_funcionario  CHAR(11)
        REFERENCES funcionario(cpf) ON DELETE CASCADE,
    nome             VARCHAR(120) NOT NULL,  -- identificador parcial
    data_nascimento  DATE,
    parentesco       VARCHAR(40),
    PRIMARY KEY (cpf_funcionario, nome)  -- chave composta = forte + parcial
);

Observe duas decisões no SQL acima. A chave primária é composta por cpf_funcionario mais nome, refletindo exatamente a regra conceitual. E o ON DELETE CASCADE traduz a dependência existencial: se o funcionário é excluído, todos os seus dependentes desaparecem junto, porque eles não têm razão de existir sem ele. Esse é o comportamento natural de uma entidade fraca, e é uma diferença que distingue uma entidade fraca verdadeira de uma entidade comum que apenas referencia outra.

Outros exemplos ajudam a fixar o padrão. Um quarto de hotel pode ser modelado como fraco em relação ao hotel, pois o número “101” se repete em milhares de hotéis e só é único dentro de um hotel específico. Um capítulo é fraco em relação ao livro, já que o capítulo “3” só faz sentido dentro de um livro determinado. O item de um pedido é fraco em relação ao pedido. Em todos esses casos, a pergunta diagnóstica é a mesma: “esta entidade conseguiria se identificar sozinha, sem emprestar a chave de outra?”. Se a resposta é não, você tem uma entidade fraca.

Entidade fraca Entidade forte Identificador parcial Chave completa
Dependente Funcionário nome cpf + nome
Quarto Hotel número id_hotel + número
Capítulo Livro número isbn + número
ItemPedido Pedido número da linha num_pedido + linha

Generalização e especialização

Agora vou te apresentar o mecanismo do modelo ER que captura relações do tipo “é um”. A generalização é o processo de abstrair características comuns de várias entidades para criar uma entidade mais genérica, o supertipo ou superclasse. A especialização é o caminho inverso: partir de uma entidade genérica e dividi-la em entidades mais específicas, os subtipos ou subclasses. São duas direções do mesmo eixo conceitual, e o coração de ambas é a herança: os subtipos herdam todos os atributos e relacionamentos do supertipo e ainda podem ter os seus próprios.

O exemplo de veículos é didático. O supertipo Veículo concentra o que é comum, como número de chassi, marca e modelo. Os subtipos Carro, Moto e Caminhão herdam esses atributos e acrescentam os específicos: número de portas para o carro, cilindrada para a moto, capacidade de carga para o caminhão. A vantagem é evidente: não repetimos chassi, marca e modelo em três lugares diferentes; centralizamos no supertipo.

erDiagram
    VEICULO ||--o| CARRO : especializa
    VEICULO ||--o| MOTO : especializa
    VEICULO ||--o| CAMINHAO : especializa
    VEICULO {
        string chassi PK
        string marca
        string modelo
    }
    CARRO {
        int numero_portas
    }
    MOTO {
        int cilindrada
    }
    CAMINHAO {
        numeric capacidade_carga
    }

O que você precisa cravar para a prova são as duas restrições que classificam toda hierarquia, porque elas se combinam em quatro arranjos possíveis. O primeiro eixo é completude: na especialização total, toda instância do supertipo precisa pertencer a pelo menos um subtipo, ou seja, não existe Veículo que não seja Carro, Moto ou Caminhão; na especialização parcial, pode haver instâncias do supertipo que não se enquadram em nenhum subtipo. O segundo eixo é disjunção: na especialização disjunta (ou exclusiva), uma instância pertence a no máximo um subtipo; na sobreposta, uma instância pode pertencer a vários subtipos ao mesmo tempo.

Combinação Significado Exemplo
Total e disjunta toda instância é exatamente um subtipo Conta é Corrente ou Poupança, e só uma
Total e sobreposta toda instância é um ou mais subtipos Pessoa é Aluno, Professor, ou ambos, mas é ao menos um
Parcial e disjunta pode não ser subtipo algum, no máximo um Funcionário pode ser Gerente, ou nenhum
Parcial e sobreposta pode não ser nenhum ou vários Produto pode ser Físico, Digital, ambos ou nenhum

Definição. Na notação tradicional, o supertipo conecta-se aos subtipos por uma linha com um círculo. Um “d” no círculo marca especialização disjunta; um “o” marca sobreposta. Linha dupla entre supertipo e círculo indica especialização total; linha simples indica parcial.

A tradução dessas hierarquias para o relacional admite três estratégias, e a escolha entre elas é um clássico de prova. Na estratégia de tabela única, criamos uma só tabela com colunas para todos os atributos de todos os subtipos, mais uma coluna discriminadora que diz qual subtipo cada linha representa; é simples e rápida para consultas, mas gera muitas colunas nulas. Na tabela por classe concreta, cada subtipo vira uma tabela que inclui também os atributos do supertipo; elimina nulos, mas dificulta consultas que precisam varrer todos os veículos. Na tabela por subclasse, criamos uma tabela para o supertipo e uma tabela separada para cada subtipo, ligadas por chave estrangeira que é também chave primária; é a mais normalizada e fiel à herança, ao custo de exigir junções.

-- Estratégia tabela por subclasse: supertipo + tabelas dos subtipos
CREATE TABLE veiculo (
    chassi   CHAR(17) PRIMARY KEY,
    marca    VARCHAR(60),
    modelo   VARCHAR(60),
    tipo     VARCHAR(10) NOT NULL CHECK (tipo IN ('CARRO','MOTO','CAMINHAO'))
);

CREATE TABLE carro (
    chassi         CHAR(17) PRIMARY KEY REFERENCES veiculo(chassi),
    numero_portas  SMALLINT
);

CREATE TABLE caminhao (
    chassi             CHAR(17) PRIMARY KEY REFERENCES veiculo(chassi),
    capacidade_carga   NUMERIC(8,2)
);

A coluna tipo funciona como discriminador e ajuda a impor a restrição de disjunção. Para garantir especialização total, você adicionaria verificações que asseguram a presença da linha correspondente na tabela do subtipo, algo que costuma exigir gatilhos ou regras de aplicação, já que o SQL declarativo puro não expressa “toda linha de veículo precisa ter um filho em exatamente uma subtabela” de forma trivial.

Agregação

Há um caso que nem entidade fraca nem generalização resolvem: quando precisamos relacionar um relacionamento inteiro com uma outra entidade. A agregação é o construtor do modelo ER estendido que trata um relacionamento, junto com suas entidades participantes, como se fosse uma entidade de nível superior, capaz de participar de outros relacionamentos.

O exemplo clássico vem de projetos. Imagine que um Funcionário trabalha num Projeto, e esse trabalho é um relacionamento. Agora queremos registrar que um Gerente supervisiona esse trabalho específico, não o funcionário em abstrato nem o projeto em abstrato, mas a combinação “funcionário trabalhando neste projeto”. Modelamos o relacionamento “trabalha” como uma agregação e a conectamos ao relacionamento “supervisiona” com o Gerente. Sem agregação, ficaríamos tentados a criar um relacionamento ternário, mas a semântica não é a mesma: a agregação deixa claro que a supervisão recai sobre uma associação já estabelecida.

erDiagram
    FUNCIONARIO }o--o{ TRABALHO : participa
    PROJETO }o--o{ TRABALHO : participa
    GERENTE ||--o{ TRABALHO : supervisiona

Na prática relacional, a agregação se materializa de forma parecida com uma entidade associativa: o relacionamento agregado vira uma tabela, e o relacionamento externo referencia a chave primária dessa tabela.

Aplicações práticas do modelo ER

Tudo o que vimos não é exercício abstrato; é o vocabulário que sustenta sistemas reais. Vou costurar os conceitos num único panorama. Num ERP, o pedido se relaciona com o cliente em 1:N, contém produtos em N:M através da entidade associativa ItemPedido, e os produtos podem ser especializados em físicos e digitais. Num sistema acadêmico, Pessoa é supertipo de Aluno e Professor, a matrícula é entidade associativa entre aluno e turma carregando nota e frequência, e professores ministram disciplinas em N:M. Num sistema de e-commerce, a avaliação depende simultaneamente de produto e usuário, o método de pagamento é supertipo de cartão e boleto, e categorias formam hierarquias.

Num sistema de saúde, Pessoa generaliza Paciente, Médico e Enfermeiro, a consulta liga paciente e médico, e a prescrição é entidade fraca dependente da consulta. Num sistema bancário, Conta é supertipo de corrente, poupança e investimento, o cliente possui contas em 1:N, e o cartão é entidade fraca da conta. Em redes sociais, o relacionamento de amizade é um auto-relacionamento N:M sobre Usuário, o comentário é fraco em relação ao post, e Conteúdo generaliza post, evento e anúncio. Em logística, o lote é fraco em relação ao produto, e Local generaliza armazém, ponto de distribuição e cliente.

erDiagram
    CLIENTE ||--o{ PEDIDO : faz
    PEDIDO ||--o{ ITEMPEDIDO : contem
    PRODUTO ||--o{ ITEMPEDIDO : compoe
    PRODUTO ||--o| PRODUTO_FISICO : especializa
    PRODUTO ||--o| PRODUTO_DIGITAL : especializa

O padrão que se repete em todos esses domínios é revelador: relacionamentos N:M viram tabelas associativas, dependências existenciais viram entidades fracas com exclusão em cascata, e categorias de “é um” viram hierarquias de generalização. Quando você consegue olhar para um requisito de negócio e mapear automaticamente para esses três padrões, está modelando como um profissional.

Atenção aos desafios reais. Sistemas de verdade têm centenas de entidades, então modularize o modelo em subsistemas lógicos para mantê-lo gerenciável. Requisitos mudam, então projete com flexibilidade. E lembre-se sempre de que cada relacionamento precisa ter significado semântico genuíno: relacionamento que não agrega informação só polui o diagrama e atrapalha a futura normalização, que veremos adiante.

Síntese para a prova. Guarde que cardinalidade máxima responde “quantos no máximo” (1 ou N) e cardinalidade mínima, a participação, responde “obrigatório ou opcional” (0 ou 1); participação total vira chave estrangeira NOT NULL, parcial vira anulável. Relacionamento 1:N coloca a chave estrangeira no lado muitos; 1:1 num dos lados; N:M sempre cria tabela própria, que vira entidade associativa quando há atributos de relacionamento. Relacionamentos ternários e n-ários não devem ser decompostos quando a semântica da tupla é irredutível, e auto-relacionamentos geram chave estrangeira para a própria tabela. Entidade fraca não tem chave própria: usa identificador parcial mais a chave da entidade forte, liga-se por relacionamento identificador com participação total e costuma exigir exclusão em cascata. Generalização classifica-se em dois eixos independentes, total ou parcial e disjunta ou sobreposta, formando quatro combinações, e traduz-se por tabela única, tabela por classe concreta ou tabela por subclasse. Agregação trata um relacionamento como entidade para que ele participe de outro relacionamento. Esses são os pontos que mais caem, e dominá-los garante boa parte das questões de modelagem conceitual.