erDiagram
PAIS ||--|| CAPITAL : possui
DEPARTAMENTO ||--o{ FUNCIONARIO : emprega
ALUNO }o--o{ CURSO : matricula
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.
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
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
}
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.