Do UML ao esquema relacional

Relações, chaves, associações n-árias e estratégias de tradução de generalizações.

TLDR

Perguntar sobre esta página

ChatGPTClaudePerplexityGrokGeminiCopiar e abrirDeepSeekCopiar e abrir

No modelo da loja, Ana está ligada às encomendas 100 e 101. Para guardar essa ligação em relações, colocamos o id de Ana em cada encomenda. A tradução deve conservar as regras do UML: referências para existência, unicidade para máximos e validação adicional quando as chaves não exprimem um mínimo.

Relações e chaves

Cada classe origina uma relação com os seus atributos simples. Uma superchave identifica um tuplo sem ambiguidade. Uma chave candidata é uma superchave mínima: retirar qualquer atributo faz perder essa capacidade. Escolhemos uma candidata como chave primária e conservamos as outras como chaves alternativas.

Se id e email identificam clientes, ambos são candidatos, desde que o email seja obrigatório e único no domínio. id, nome é superchave mas não candidata, porque id já chega. No nosso exemplo, emails desconhecidos são permitidos, pelo que email não é chave candidata conceptual, embora exista uma restrição UNIQUE em SQL.

Uma chave estrangeira referencia uma chave candidata de outra relação ou da própria relação. Pode ser composta. As referências de uma chave composta têm de corresponder ao mesmo tuplo alvo, não a tuplos separados para cada coluna.

Associações um para muitos

Cada encomenda tem um cliente, por isso:

Cliente(id, nome, email)             PK: id
Encomenda(id, data, idCliente)        PK: id
Encomenda.idCliente → Cliente.id

A estrangeira fica em Encomenda, o lado muitos. Uma única coluna em Cliente não poderia guardar várias encomendas.

Se a encomenda tem exatamente um cliente, idCliente é NOT NULL. Se pudesse não ter cliente, admitiria NULL.

Essa referência garante que o cliente indicado existe. Não garante que todos os clientes tenham encomendas. A exigência 1..* junto de Encomenda precisa de outro mecanismo, como validação transacional ao confirmar os dados.

Ver a alternativa com relação própria

Também podemos criar uma relação separada Faz(idEncomenda, idCliente) cuja chave é idEncomenda. A unicidade da encomenda mantém o máximo de um cliente. As duas estrangeiras têm de ser obrigatórias. Mesmo assim, uma encomenda pode não ter linha em Faz: garantir exatamente um cliente exige validar também essa participação. Esta alternativa é útil quando a ligação tem identidade ou evolução próprias; a estrangeira integrada costuma ser mais simples.

Muitos para muitos e um para um

Uma associação muitos para muitos origina uma relação com as duas chaves:

Item(idEncomenda, idProduto, qtd, precoUnitario)
PK: (idEncomenda, idProduto)
idEncomenda → Encomenda.id
idProduto → Produto.id

O par impede duas linhas do mesmo produto na mesma encomenda. A quantidade e o preço da compra pertencem à associação.

Acrescentar um id artificial é possível. Nesse caso, conserva UNIQUE(idEncomenda, idProduto) se essa continuar a ser a regra.

Numa associação um para um, uma estrangeira isolada ainda permitiria várias referências ao mesmo tuplo. Acrescenta UNIQUE. Para um cartão que pertence a exatamente um cliente e um cliente com no máximo um cartão:

Cartao(numero, idCliente)
PK: numero
FK: idCliente → Cliente.id, NOT NULL, UNIQUE

Esta tradução não obriga cada cliente a ter cartão. Se ambos os lados tiverem participação obrigatória, a fusão pode evitar a obrigação de manter duas linhas correspondentes, mas só faz sentido quando os objetos e os seus ciclos de vida justificam essa escolha.

Conversão completa da loja

As chaves garantem uma linha por cliente, produto e encomenda, e uma linha por par encomenda/produto. O mínimo de um produto por encomenda continua por garantir: podemos criar uma encomenda e ainda não criar Item. Um NOT NULL nas estrangeiras de Item não muda isso, porque uma tabela vazia não tem linhas que o violem.

Há uma diferença entre armazenar um estado intermédio durante a criação e aceitar esse estado como encomenda confirmada. A aplicação pode inserir a encomenda e as suas linhas numa transação, validando o mínimo antes de confirmar. A regra precisa de estar definida para todas as operações que possam quebrá-la, incluindo remover a última linha.

Associações reflexivas e qualificadas

Uma pessoa com no máximo um progenitor registado usa uma referência à própria tabela:

Pessoa(id, nome, idProgenitor)
PK: id
FK: idProgenitor → Pessoa.id, admite NULL

Uma associação muitos para muitos entre pessoas exige uma relação própria com papéis distintos. Em Amizade(idPessoaA, idPessoaB), uma chave no par elimina duplicação na mesma ordem. Para representar uma amizade não orientada uma única vez, podemos exigir idPessoaA < idPessoaB, além de duas estrangeiras. Essa condição proíbe também a autorreferência.

Para os números de membro locais ao clube:

Membro(idClube, numeroMembro, idPessoa)
PK: (idClube, numeroMembro)
FK: idClube → Clube.id; idPessoa → Pessoa.id
UNIQUE: (idClube, idPessoa), se cada pessoa só tiver uma inscrição por clube

O número sozinho não é chave. A última unicidade é uma hipótese adicional e deve ser imposta apenas se o domínio a exigir.

Associações n-árias

Traduz uma associação ternária para uma relação com as chaves dos três participantes e os atributos da ligação:

Fornecimento(idFornecedor, idProduto, idProjeto, quantidade)

Se todos os máximos forem muitos, o triplo é normalmente a chave da associação.

Se para cada Produto/Projeto houver no máximo um fornecedor, temos a dependência (idProduto, idProjeto) → idFornecedor. O par Produto/Projeto passa a identificar o fornecimento. Não escolhas mecanicamente o triplo como chave candidata quando a multiplicidade já torna parte dele redundante.

As estrangeiras verificam existência dos participantes. A chave e outras unicidades verificam as multiplicidades máximas. Mínimos que exigem a participação de todos os objetos não resultam destas estrangeiras.

Traduzir generalizações

Há três estratégias principais. Considera Pessoa(id, nome) com subclasses Aluno(curso) e Docente(departamento).

Uma relação por classe mantém Pessoa(id, nome), Aluno(id, curso) e Docente(id, departamento). O id das subclasses é simultaneamente primária e estrangeira para Pessoa. Representa naturalmente uma pessoa que é aluno e docente, mas consultar o nome e o curso exige uma junção.

Uma relação por tipo concreto guarda todos os atributos de cada tipo. Se a generalização for completa e disjunta, podemos usar apenas Aluno(id, nome, curso) e Docente(id, nome, departamento), sem uma Pessoa autónoma. Uma primária em cada tabela não impede repetir o mesmo id entre Aluno e Docente, por isso, se o id identifica pessoas globalmente, é preciso garantir essa unicidade entre os tipos e a disjunção exigida.

Se houver sobreposição, não basta copiar a mesma pessoa para duas tabelas sem coordenação: podemos precisar de tipos para as combinações de subclasses, como AlunoDocente, e o número de combinações pode crescer depressa.

Uma relação única guarda os atributos de todas as classes, por exemplo Pessoa(id, nome, eAluno, eDocente, curso, departamento). Os indicadores permitem distinguir pertença de um atributo específico desconhecido. Um único discriminador com valores Aluno/Docente serve uma generalização disjunta, mas não uma sobreposta. Precisamos de CHECKs e de regras para colunas que só se aplicam a certas subclasses.

Na estratégia por classe, duas estrangeiras para Pessoa não garantem completude nem disjunção. Uma Pessoa pode não estar em nenhuma subclasse ou estar nas duas. Estas condições precisam de validação própria se o modelo as exigir.

Composição e elementos derivados

Composição e agregação traduzem-se estruturalmente como associações. A composição pode justificar ON DELETE CASCADE, mas a política depende de como o sistema representa a eliminação. Arquivar uma encomenda não é apagar os seus tuplos.

Um total derivado pode ser calculado numa consulta ou vista. Se o guardares, introduzes uma obrigação de consistência com as linhas. Um valor derivado não é automaticamente uma coluna independente e não deve receber alterações arbitrárias.

Conferir a tradução

Para cada chave, explica por que identifica o tuplo e por que nenhum subconjunto chega. Para cada associação, localiza as estrangeiras, o máximo imposto por unicidade e os mínimos ainda por implementar. Depois testa um exemplo permitido e um proibido. Um esquema com todas as setas desenhadas pode continuar a aceitar estados que o UML proíbe.

Exercícios

Traduzir máximo e mínimo

Cada Departamento tem um ou mais Funcionários. Cada Funcionário pertence a exatamente um Departamento. Propõe tabelas e restrições e indica a regra que uma FK simples não garante.

Pista
Coloca a FK no lado muitos.
Mais uma pista

Uma tabela de funcionários vazia tem alguma linha que viole NOT NULL?

Solução

Departamento(id, nome), com PK id, e Funcionario(id, nome, idDepartamento), com PK id e FK idDepartamento para Departamento.id, NOT NULL. A FK garante que o departamento do funcionário existe; NOT NULL exige que cada funcionário indique um. Não garantem que cada departamento tenha pelo menos um funcionário. Essa regra precisa de validação que inclua criação de departamentos e remoção ou mudança do último funcionário, normalmente numa operação transacional.

Confere a tua resposta:

  • Pus a FK em Funcionario com NOT NULL.
  • Identifiquei o mínimo de um funcionário como regra adicional.

Erros frequentes

Uma FK na tabela Departamento só guardaria um funcionário. Um CHECK local não conta linhas noutra tabela em SQLite.

Voltar à explicação
A chave de uma associação ternária

Cada par Produto/Projeto tem no máximo um registo de fornecimento, com um fornecedor e uma quantidade. Um fornecedor pode servir vários pares. Na relação Fornecimento(fornecedor, produto, projeto, quantidade), qual é a candidata, assumindo que não há outras dependências?

Pista

A regra determina fornecedor a partir de produto e projeto.

Solução

A candidata é (produto, projeto). O par determina fornecedor e quantidade. Produto sozinho pode ocorrer em projetos diferentes e projeto sozinho pode receber produtos diferentes, por isso nenhum deles chega. O triplo fornecedor/produto/projeto é uma superchave, mas não é mínimo. Declara FKs para os três participantes e NOT NULL nas colunas da candidata.

Confere a tua resposta:

  • Encontrei a candidata produto/projeto.
  • Provei determinação e minimalidade.

Erros frequentes

Usar sempre o triplo ignora a multiplicidade máxima junto de Fornecedor. A existência dos participantes não prova a unicidade do fornecimento.

Voltar à explicação
Chave composta e restrições que ficam por garantir

Uma encomenda tem id, data, exatamente um cliente e pelo menos um produto. Cliente e Produto têm id e nome; Produto tem preço atual. Cada par encomenda/produto ocorre uma vez, com quantidade e preço acordado.

Traduz para relações com chaves e estrangeiras. O que acontece ao inserir uma encomenda sem Items? Se Item ganhar um id artificial, que restrição deve continuar a existir?

Pista
Uma coluna em Cliente não consegue guardar todas as encomendas desse cliente.
Mais uma pista
Compara identificar a linha por um id novo com impedir duas linhas do mesmo par.
Solução

Usa Cliente(id,nome), Produto(id,nome,precoAtual), Encomenda(id,data,idCliente) e Item(idEncomenda,idProduto,qtd,precoUnitario). As três primeiras têm chave id. Item tem chave (idEncomenda,idProduto): a encomenda sozinha admite vários produtos; o produto sozinho admite várias encomendas.

Encomenda.idCliente referencia Cliente e é obrigatório. As duas colunas da chave de Item referenciam Encomenda e Produto e também são obrigatórias. Uma encomenda sem Items não viola essas restrições: não há nenhuma linha filha inválida. É preciso validar o mínimo ao confirmar uma encomenda e ao remover a última linha.

Com Item(id,...), a chave id não impede dois ids para o mesmo par. Conserva UNIQUE(idEncomenda,idProduto) e a obrigatoriedade das duas colunas.

Confere a tua resposta:

  • Escrevi as quatro relações e as três referências.
  • Justifiquei a chave composta de Item.
  • Identifiquei o mínimo que as chaves não garantem.
  • Conservei a unicidade do par ao acrescentar um id artificial.

Erros frequentes

Um id artificial não substitui as unicidades do domínio. Uma estrangeira só verifica referências existentes nas linhas filhas.
Voltar à explicação

À tua maneira

Aparência

Modo de cor
Tema
Cor de destaque

Leitura

Fonte de leitura

Álgebra, lógica e uma ideia de cada vez.

Código

Fonte do código

Navegação

Cursor de leitura, sem edição. h, j, k, l movem; / pesquisa nesta página. v seleciona caracteres; V seleciona linhas; y copia. Tab permite destacar ou comentar. Espaço ou Enter abre blocos recolhíveis. Nos cartões, h, j, k, l mudam o foco.

CSS personalizado

Ativa uma sugestão ou guarda o teu CSS. Só muda este navegador.

Usa --page, --text e --accent para acompanhar o tema.

Seletores e recuperação

.site-header, .prose, .course-card, [data-reading-history], [data-playground] e .page-actions.

Abre a aparência com a tecla vírgula ou no rodapé. Se o CSS esconder os controlos, abre esta página sem CSS personalizado.

Pesquisar

Escreve para pesquisar em todo o site.

para escolher · Enter para abrir · Esc para fechar

Copiar fórmula

A cópia automática não está disponível. Copia o LaTeX abaixo.

Pesquisar nesta página

Enter procura · n / N repetem · Esc cancela. Ctrl / Cmd K pesquisa todo o site.

Atalhos de teclado

Clica numa tecla para a mudar. Esc cancela. Backspace desativa.

PesquisarCtrl / Cmd K

Os atalhos não interferem enquanto escreves. Tab e Enter funcionam sempre.