Álgebra relacional 101
Vindo de uma thread do Twitter.
Eu disse que desenho queries com álgebra e me perguntaram “como assim”? Pois bem, aqui venho mostrar para vocês meu segredo: a álgebra relacional.
Tuplas
Tuplas são elementos criados em cima de outros elementos. Dados dois elementos e , posso criar com eles duas tuplas distintas:
Tuplas são posicionais, mas também podem ser nomeadas. Pegando o exemplo acima para tuplas da forma :
Quando lidamos com tuplas posicionais é como se o índice fosse o “nome” associado ao elemento:
Note que, como estamos colocando nomes nas posições, as seguintes tuplas são iguais:
Em tese, simplesmente aplicar a “tuplificação” em cima de dois elementos gera uma nova tupla de 2 posições. Por exemplo, usando os elementos e :
Porém, por uma questão de vício e para poupar alguns passos, exceto se comentado o oposto, neste artigo vou deixar os elementos das tuplas expostos na tupla resultado:
Para tuplas nomeadas com produtos cartesianos:
Conjuntos e bags
Conjuntos são caracterizados por uma operação central: . Essa operação tem no lado direito um conjunto e no lado esquerdo um elemento. Em cima apenas disso e sem grandes detalhes você consegue derivar a teoria ingênua dos conjuntos (com todos os seus paradoxos que não vem ao caso discorrer aqui agora). Esse operador resulta em um resultado booleano: ou o elemento pertence ao conjunto ou não.
A partir disso e de operadores de predicado booleanos, podemos derivar outros operadores. Aqui operadores entre conjuntos:
União:
Interseção:
Remoção:
Está contido:
Está contido (própriamente):
Produto cartesiano:
Conseguimos em cima disso fazer operações de conjuntos com elementos, a partir da definição a seguir para um conjunto e um elemento , como “somar”:
e “subtrair”:
Note que, nessa operação de “somar” pode gerar o mesmo conjunto se tivermos . De modo semelhante, a “subtração” pode resultar no mesmo conjunto se .
Bags são parecidas com conjuntos, ela oferece a operação de “pertence” tal qual conjuntos, mas não apenas isso. Bags permitem repetição de elementos. Portanto, ao “somar” um elemento a uma bag obteremos sempre uma nova bag. A remoção/”subtração” de elementos, entretanto, pode resultar no mesmo conjunto.
Para representar a multiplicidade temos a função . Essa função mostra quantas vezes o elemento aparece na bag . Agora, a operação “pertence” pode ser escrita em cima da operação de multiplicidade:
E o “não pertence”:
Operador de “soma” de um elemento a bag:
Operador de “subtração” de um elemento a bag:
A união de duas bags é a soma das multiplicidades dos elementos:
E interseção é o mínimo das multiplicidades:
Produto cartesiano é semelhante ao de conjuntos, porém permite repetição:
Álgebra relacional
Vamos pegar um caso base para motivar o estudo:
Dada uma bag de usuários composta da tupla nomeada de forma
(id, nome, role, data de criação)
e a bag de acessos cujas tuplas são
da forma (id acesso, id user, instante)
, precisamos pegar os usuários
que acessaram o sistema entre os instantes e j, com role >= e
dt criação > . Desses elementos, só interessa exibir o nome do usuário.
As operações de álgebra relacional são feitas em cima de bags. Exceto se provado
o contrário, podemos sempre assumir que estamos lidando com bags. Por exemplo,
mesmo que usuários
fosse um conjunto, eu poderia usar um operador de álgebra
relacional e, de lá, obter uma bag.
Vamos assumir que id
é garantido ser único para toda e qualquer tupla
em usuários
. Portanto, temos que usuários
é, na prática, um conjunto.
Porém, nada garante que o role
seja distinto. Portanto, a seguinte
coleção é válida para usuários
:
E em cima disso posso simplesmente projetar o role de cada linha:
Tá, e para o problema, como podemos resolver usando álgebra relacional?
Tá, vamos aos poucos destrinchar a solução. Mas, antes, falar de algo que não aparece na solução, para depois mostrar o que de fato aparece.
Uma das coisas que podemos fazer com duas bags distintas é o produto cartesiano entre elas. Por exemplo, entre usuários e acessos:
Porém, isso não nos dá muita coisa. Precisamos selecionar apenas as tuplas em que o id do usuário seja igual ao id user de acesso:
Tá vendo aqui o produto cartesiano e a seleção com base na igualdade de valores das tuplas? A isso damos o nome de junção natural. A junção natural é indicada pelo operador e, em seu subscrito, a condição de junção, um predicando envolvendo as tuplas. Daí, temos que a junção natural é como se fosse um produto cartesiano seguido de uma seleção:
Bem, acabou que pra falar da junção natural acabei falando bastante também da seleção. A seleção pode ser feita em cima de uma simples tupla, não precisa ser a relação de junção entre dois conjuntos tal qual foi com a junção natural.
Por exemplo, podemos pegar os números naturais pares:
A seleção é indicada pelo . Ele recebe como argumento uma bag e deixa passar apenas as tuplas que satisfaçam o predicado no subscrito. Posso ter vários subscritos, é a mesma coisa de ter várias seleções distintas: precisa satisfazer todos os predicados para a tupla ser selecionada.
Agora, a projeção. Pois bem, com a projeção criamos mecanismos de manipular a tupla em si, não apenas anexar coisas no final dela. Usamos o operador e em seu subscrito o que queremos projetar. Por exemplo, os nomes da bag :
Aqui eu usei para indicar a definição de uma nova bag:
Não sei se a notação clássica de álgebra relacional lida com isso ou se só usa o sinal de mesmo (eu acho que é esse segundo caso). Mas quis deixar bem enfatizado que estou definindo uma bag nova.
Isso foi o básico sobre álgebra relacional. Ela suporta as operações de conjunto também, como união, interseção etc, como demonstrado na seção anterior.
Extensões
Existem algumas extensões comuns a álgebra relacional. A presença do nulo (indicado por ), junções laterais, agregações (não vi consenso sobre isso, usava ) e outras coisas legais.