Álgebra relacional
Operações sobre conjuntos, renomeação, junções, divisão e equivalências.
Para saber quem comprou em 5 de janeiro, filtramos Encomenda pela data. Depois ligamos o resultado a Cliente e escolhemos os atributos da resposta.
A álgebra relacional compõe essas operações. Cada uma recebe relações e devolve uma relação. Usamos aqui a álgebra clássica, com conjuntos, sem duplicados e sem ordem de tuplos.
Os operadores essenciais
A seleção conserva os tuplos de R que satisfazem o predicado p. conserva Teclado e Monitor, com todos os seus atributos. A seleção não escolhe colunas.
A projeção conserva os atributos X e elimina tuplos repetidos. Se dois produtos distintos se chamarem Rato, contém Rato uma vez. Uma projeção SQL precisa de DISTINCT para reproduzir essa semântica de conjuntos.
O produto cartesiano combina cada tuplo de R com cada tuplo de S. Se houver m e n tuplos, o resultado tem tuplos, assumindo nomes de atributos desambiguados. Se uma relação estiver vazia, o produto está vazio.
A projeção clássica só escolhe atributos existentes. A projeção generalizada admite também expressões calculadas e nomes novos, como no exemplo seguinte:
É a forma algébrica da lista de SELECT em SQL: cada coluna da resposta indica a expressão e o nome.
A renomeação altera o nome de uma relação ou dos seus atributos, conservando os valores. Escrevemos, por exemplo, .
A ordem indicada corresponde à dos atributos na notação do esquema original. Outras convenções de renomeação são possíveis. Explicita a usada.
Na demonstração, a seleção usa preço ≥ limite, com preços em euros. Acrescentámos um segundo produto chamado Rato, de id 13 e preço 30, para observar duplicados. Com limite 30, vê os ids 10, 12 e 13; baixa para 25 e projeta o nome. Entram quatro produtos, mas a projeção só tem três tuplos porque os dois Ratos dão o mesmo nome.
3 produtos selecionados; 3 tuplos no resultado.
| id | nome | preço |
|---|---|---|
| 10 | Teclado | 45 |
| 12 | Monitor | 180 |
| 13 | Rato | 30 |
No estado inicial, o Rato de 25 euros fica de fora. Com limite 25, entram os quatro produtos; projetar só o nome reúne os dois Ratos num único tuplo. Com limite 185, o resultado fica vazio.
Junções
A teta-junção equivale a . O predicado pode usar igualdade ou outras comparações. Numa equijunção, comparamos atributos por igualdade. Atributos do mesmo nome precisam de qualificação ou renomeação.
A junção natural impõe igualdade em todos os atributos de nome comum e conserva uma cópia de cada atributo comum.
Se não houver nenhum nome comum, equivale ao produto cartesiano. Se houver vários, todos participam, mesmo que não fossem a ligação que tinhas em mente.
Na loja, Cliente(id,nome,email) e Encomenda(id,data,idCliente) têm id em comum, mas um id de cliente não é um id de encomenda. Uma junção natural compararia estes ids erradamente. Usa a condição Cliente.id = Encomenda.idCliente ou renomeia o id do cliente antes da junção natural.
A junção natural de R(A,B) com S(B,C) liga pelo B. A de R(A,B) com T(C,D) produz todas as combinações, porque não há nomes comuns.
Semijunção e agregação
A semijunção conserva apenas os tuplos de R com alguma correspondência em S. Não acrescenta as colunas de S. Na álgebra de conjuntos, podemos escrevê-la como .
Na loja, a semijunção seguinte dá Ana e Rui, uma vez cada, com os atributos de Cliente:
Mia fica de fora. Ana ter duas encomendas não cria duas cópias da cliente. Em SQL, um EXISTS sobre Cliente exprime este teste; uma junção seguida de projeção sem DISTINCT pode manter repetições.
Contar ou somar exige uma extensão da álgebra clássica. Aqui usamos para agrupamento e agregação. Os slides também apresentam agregações como uma extensão de ; identifica sempre a convenção usada.
Agrupamos pelo cliente e chamamos n à contagem:
| idCliente | n |
|---|---|
| 1 | 2 |
| 2 | 1 |
Duas encomendas para Ana e uma para Rui. Mia não aparece, porque não há tuplos seus em Encomenda. Para mostrar também clientes com zero, é preciso conservar o universo Cliente, como na junção externa em SQL.
União, diferença e interseção
contém os tuplos que estão em pelo menos um lado; , os que estão nos dois; , os que estão em R e não em S. Os esquemas têm de ser compatíveis: mesmos atributos, com domínios correspondentes. Renomeia ou projeta antes da operação se necessário.
A diferença não é comutativa. CompraramTeclado − CompraramRato responde a quem comprou teclado e não rato; trocar os lados muda a pergunta. A condição “há uma compra que não é rato” também é diferente de “não há qualquer compra de rato”.
A interseção pode ser escrita só com diferença: . Estes operadores não somam contagens de duplicados, porque a álgebra clássica usa conjuntos.
Uma pergunta completa
Queremos os nomes dos clientes com encomendas em 5 de janeiro de 2026. Seja C a renomeação de Cliente que muda id para idCliente. Primeiro filtramos Encomenda, depois juntamos pelo atributo comum idCliente e finalmente projetamos nome:
Lê a árvore de baixo para cima. A junção recebe as encomendas filtradas e os clientes com o id renomeado.
As tabelas intermédias, com os dados da loja:
tem uma linha:
| id | data | idCliente |
|---|---|---|
| 100 | 2026-01-05 | 1 |
acrescenta os atributos de Cliente, incluindo email:
| id | data | idCliente | nome | |
|---|---|---|---|---|
| 100 | 2026-01-05 | 1 | Ana | ana@example.test |
A projeção final devolve o conjunto com um tuplo, {Ana}. Se Ana tivesse duas encomendas nesse dia, a projeção continuaria a mostrá-la uma vez. Para identificar pessoas, conserva o id.
As folhas da árvore são relações e cada nó interior é um operador. A seleção e a renomeação recebem uma relação cada; a junção recebe duas. Ao transformar a árvore, conserva todas as entradas e as colunas de que os operadores precisam.
Clientes sem encomendas
Não podemos descobrir uma ausência filtrando apenas encomendas existentes. Precisamos do conjunto de candidatos e de retirar os que aparecem:
Com os dados da loja, Todos é e Compradores é ; a diferença é . Para obter nome e email, juntamos esse resultado com Cliente. A renomeação tornou os esquemas compatíveis.
Divisão: perguntas com todos
Se regista pares e contém os valores exigidos, devolve os X que estão ligados a todos os Y de S. O esquema da resposta contém os atributos de R que não estão em S.
Suponhamos Comprou(idCliente,idProduto) com e Exigido(idProduto) com 10 e 11. O cliente 1 comprou todos os produtos exigidos; o 2 não comprou o 10. A divisão devolve 1.
Pode ser construída com operadores básicos:
O produto enumera pares que deveriam existir. A diferença encontra os que faltam:
| Candidato | Produto exigido | Está em Comprou? |
|---|---|---|
| 1 | 10 | sim |
| 1 | 11 | sim |
| 2 | 10 | não, falta |
| 2 | 11 | sim |
Só falta (2,10). Projetar o cliente dessa falta dá {2}. Retirá-lo dos candidatos {1,2} deixa {1}, a resposta. Não basta filtrar compras do produto 10 ou 11: isso também aceitaria o cliente 2, que só cumpriu um dos requisitos.
Se S estiver vazia, não falta nenhum requisito e a divisão devolve . Repara no universo dos candidatos: um cliente que não aparece em Comprou não pertence à projeção inicial. Se quisermos todos os clientes, incluindo os sem compras quando não há requisitos, usamos Cliente como universo e a mesma ideia de retirar faltas. A divisão em SQL explicita esta escolha.
Equivalências e execução
Podemos separar uma seleção conjuntiva: . Se p só usa atributos de R, podemos aplicar essa seleção antes de juntar R com S. Isso pode reduzir o número de tuplos intermédios sem mudar a resposta.
A projeção também pode reduzir atributos antes de uma junção, mas tem de conservar os usados pela condição e os necessários ao resultado. Se retirarmos idCliente antes de ligar as encomendas aos clientes, perdemos a ligação.
Por exemplo, para e :
À esquerda, a junção obriga também B de R a ser 1. À direita, os dois lados já foram filtrados por esse mesmo valor, por isso todas as combinações concordam em B e podemos retirá-lo antes do produto.
Retirar B de R antes do filtro, usando , perde a obrigação. Com e , as expressões corretas devolvem vazio, mas esta última devolve . Já não existe B do lado de R para comparar. Antecipar a projeção exige conferir filtros além das colunas da junção.
Estas equivalências explicam transformações de um otimizador. Não determinam a ordem física exata que o SGBD seguirá. Junções externas e a lógica de NULL exigem cuidados adicionais; não lhes apliques automaticamente todas as identidades da álgebra clássica.
Exercícios
Uma junção pelo atributo errado
Cliente(id,nome) e Encomenda(id,idCliente) são juntadas naturalmente. Que condição é usada? Como obter a ligação pretendida?
Pista
A junção natural usa todos os nomes comuns, sem conhecer o seu significado.
Solução
A condição natural é Cliente.id = Encomenda.id, porque id é o nome comum. Isso compara uma chave de cliente com uma chave de encomenda. Usa uma teta-junção com Cliente.id = Encomenda.idCliente, qualificando os atributos, ou renomeia Cliente.id para idCliente e faz depois a junção natural. A mera existência de nomes comuns não prova a ligação correta.
Confere a tua resposta:
- Identifiquei a igualdade realmente usada.
- Propus uma ligação correta.
Divisor vazio e universo de candidatos
Comprou(cliente,produto) contém (1,10),(1,11),(2,11). Exigido(produto) está vazio. Cliente contém 1,2,3. Qual o resultado de Comprou ÷ Exigido e de uma pergunta universal sobre todos os clientes?
Pista
A divisão clássica parte da projeção dos candidatos de Comprou.
Solução
A divisão clássica devolve {1,2}, que é a projeção cliente de Comprou. A pergunta que parte da relação Cliente devolve {1,2,3}, pois não há produto exigido que falte a qualquer cliente. O cliente 3 não entra na primeira porque não aparece no universo inicial, não porque tenha falhado um requisito.
Confere a tua resposta:
- Distingui os dois conjuntos de candidatos.
- Expliquei a satisfação universal sem requisitos.
Correspondências, tuplos e grupos
Cliente contém (1,Ana), (2,Rui), (3,Mia). Encomenda contém (100,1), (101,1), (102,2), com atributos idEncomenda e idCliente. Quantos tuplos produz a junção por Cliente.id = Encomenda.idCliente? E a semijunção que conserva apenas Cliente? Indica ainda o resultado de agrupar Encomenda por idCliente e contar os tuplos de cada grupo.
Pista
A junção conserva cada encomenda; a semijunção conserva os clientes com alguma correspondência.
Solução
A junção produz três tuplos, um por encomenda. A semijunção produz dois, (1,Ana) e (2,Rui), sem atributos de Encomenda e sem repetir Ana. O agrupamento de Encomenda produz (1,2) e (2,1). Não produz (3,0), porque não há linhas de Mia nessa relação. Para incluir Mia, temos de partir de Cliente e conservar os clientes sem correspondência.
Confere a tua resposta:
- Distingui os três tuplos da junção dos dois clientes da semijunção.
- Obtive as contagens 2 e 1, sem criar um grupo para Mia.
Erros frequentes
Não contes encomendas como clientes distintos. A agregação de uma relação não inventa grupos para valores que não aparecem nela.
Ausência e diferença de conjuntos
Cliente(id,nome) contém (1,Ana), (2,Rui), (3,Mia). Encomenda(id,idCliente) contém (100,1), (101,1), (102,2). Comprou(idCliente,idProduto) contém (1,10), (1,11), (1,12), (2,11).
Escreve expressões de álgebra para os ids de clientes sem encomendas e para os ids de clientes que compraram teclado, id 10, mas nunca rato, id 11. Apresenta os conjuntos intermédios. Filtrar idProduto ≠ 11 responde à segunda pergunta?
Pista
Mais uma pista
Solução
Para os clientes sem encomendas, projeta id de Cliente: {1,2,3}. Projeta idCliente de Encomenda e renomeia-o para id: {1,2}. A diferença dá {3}, Mia.
Para teclado sem rato, usa
O primeiro conjunto é {1}, o segundo {1,2}. A diferença é vazia. Selecionar compras com idProduto ≠ 11 daria {1} pela compra do teclado e do monitor, apesar de Ana também ter comprado rato.
Confere a tua resposta:
- Obtive clientes sem encomendas a partir de Cliente.
- Renomeei os atributos para tornar a diferença compatível.
- Distingui comprar algo diferente de rato de nunca comprar rato.
Erros frequentes
Uma projeção apaga a condição necessária
Considera R(A,B) e S(B,C), com valores inteiros, sem hipóteses de chaves. Compara:
Quais são equivalentes em todas as instâncias? Constrói um contraexemplo com um tuplo em cada relação e calcula as três respostas. Usa a semântica de conjuntos da álgebra clássica.
Pista
Solução
E1 e E2 são equivalentes. Em E1, S foi filtrada por B=1, e a junção obriga o B de R a ser também 1. Todos os tuplos dos dois conjuntos filtrados têm o mesmo B; o produto das projeções A e C dá exatamente os mesmos pares.
E3 não filtra R por B=1. Escolhe R com (3,4) e S com (1,2). E1 dá vazio porque 4 não é 1. E2 dá vazio porque o filtro de R elimina o seu único tuplo. E3 projeta R em {3} e combina-o com o tuplo de S, dando {(3,2)}. A projeção eliminou B antes de exigir a condição.
Confere a tua resposta:
- Justifiquei a equivalência de E1 e E2 para qualquer instância.
- Construí e calculei um contraexemplo para E3.
- Identifiquei o filtro de R que desapareceu.