English

Relational Algebra: self-join + set subtraction

In Relational Algebra exercises, it may happen that is asked something like “detect the minimum/maximum of the values for a table attribute”, or something of different nature, but that may be traced back to these.
The solution in these cases is using a “self-join” followed by a “set difference”.
This document will explain what’s the logical reasoning that justify the usage of these two operations.

It is took for granted that the reader knows Relational Algebra well.
Consider the following example table T, defined as T(A), with tuples:

+===+
| A |
+===+
| 1 |
| 2 |
| 3 |
| 4 |
+---+

The request is: “Find the minimum value for A in T”.
A quick glance suggests that the value of interest is 1, but how to get to the result with Relational Algebra?

To solve this kind of problem, one must imagine the values for A, as if they were a set S = {1, 2, 3, 4}, and then, consider its cartesian product SxS:

SxS = {
    (1, 1),
    (1, 2),
    (1, 3),
    (1, 4),
    (2, 1),
    (2, 2),
    (2, 3),
    (2, 4),
    (3, 1),
    (3, 2),
    (3, 3),
    (3, 4),
    (4, 1),
    (4, 2),
    (4, 3),
    (4, 4)
}

Now, imagine to filter these pairs in respect to the condition LT > RT (left term > right term); The result of said filtering is the following relation R_>:

R_> = {
    (2, 1),
    (3, 1),
    (3, 2),
    (4, 1),
    (4, 2),
    (4, 3),
}

Paying attention to the LTs of R_>, one may notice there’s no pair for which LT = 1.
The value of interest is missing, but that’s the point, thanks to this, the set difference can finally be used.
Let K = {2, 3, 4}, that is, the set of LTs in R_>; All that is left, is to subtract K from S:

S - K = {1, 2, 3, 4} - {2, 3, 4} = {1}

Wow, the minimum has been found!

In Relational Algebra, this is translated as self-join (=theta join of T with a renamed copy of it) of T, subtracted (set difference) from T itself.
Ti solve the quesit, it is sufficient to use the following operations:

K := π_{A}(T ⋈_{A > A'} ρ_{A'/A}(T));
r :=  T - K

Please note that the notation _{...} indicates the subscript of an operator (the contents being in braces).
For example, the operation ρ_{A'/A}(T) is read as “rename the attribute A for table T as A’”, “A’/A” is in the subscript of ρ and T is in round brackets.

Here r is the wanted result.
When there is an order among values (the so-called “order relation”), a self-join with a condition like LT op RT, followed by a set subtraction, is usually sufficient to solve this particular class of problems.
The comparison operator op depends strictly on the results one wants to get.

Self-join is thus, the relational equivalent of comparing all the possible values of set S among themselves; It’s obvious that if the greater values are asked, THERE WILL NEVER BE the minimum in LT, bacause it is the only value which IS NOT GREATER than the others; In the same manner, if the lower values are asked, THERE WILL NEVER BE the maximum in LT, because it IS NOT LOWER than the others.

This helps in understanding why one needs the self-join first and then the subtraction: Self-join’s purpose is to accumulate useless values as LTs, so that later, it will be possible to subtract them from the table that serves as a whole, in order to get the values of interest as result.

In case of minimum/maximum detection, the reasoning can be summarized in:

  • To detect the minimum, first self-join with condition LT > RT, then, subtract the result from the set of LTs;
  • To detect the maximum, first self-join with condition LT < RT, then, subtract the result from the set of LTs;

More complex problem solvable with self-join + subtraction

Here follows the discussion of a more complex problem, which can be traced back to the previous solving reasoning.
Suppose the database has the following tables with constraints:

Organization(ID, Name);
Person(ID, Name, Gender);
Employee(IDOrganization, IDPerson);

ID is primary key for Organization;
ID is primary key for Person;
Attributes tuple (IDOrganization, IDPerson) is primary key for Employee;
IDPerson references Person(ID);
IDOrganization references Organization(ID);

It is asked to “Return the organizations that exclusively employ women”.
This means that all organizations that employ a mixed workforce, must be excluded.
Despite it not being a minimum/maximum problem, the reasoning is the same: At first, a self-join is needed, in order to get the set of organizations that have a mixed workforce (that is, the useless tuples).
To do this, one can make the view v_1, defined as:

v_1 = π_{IDOrganization, Gender}(Employee ⋈_{IDPerson = ID} Person);

This view collects the organization IDs and the gender of the employees in a single table.
Now it’s time for the self-join: The employees (in actuality, only their genders) belonging to the same organization, must be combined with each other, in order to find the tuples, in which the two employees have opposite genders.
This works because, when an employee is combined with the colleagues, and all of them have the same gender, it is guaranteed that there won’t be tuples where the first employee is of the opposite gender to the second one.

To get this result, a self-join of v_1 is needed:

v_2 = π_{IDOrganization}(v_1 ⋈_{Gender ≠ Gender' ^ IDOrganization = IDOrganization'} ρ_{IDOrganization',Gender'/IDOrganization,Gender}(v_1))

v_2 collects the organizations (IDs) that employ both genders, that is, the useless tuples.
It’s now time to subtract v_2 from v_1, in order to get v_3, that is, a view containing all the organizations (IDs) which exclusively employ men or women, not both:

v_3 = π_{IDOrganization}(v_1) - v_2

It’s not over yet, it was asked to return the organizations (IDs) that exclusively employ women.
To overcome the obstacle, it sufficient to use a theta-join between v_3 and v_1, in order to exclude male employees.
The final operation is, thus, the following:

r = π_{IDOrganization}(v_3 ⋈ {Gender ≠ 'M' ^ IDOrganization = IDOrganization'} ρ_{IDOrganization'/IDOrganization}(v_1))

r is the desired result, that is, the set of organizations (IDs) that exclusively employ women.

Italiano

Algebra relazionale: self-join + sottrazione insiemistica

Negli esercizi di Algebra relazionale, può capitare che vengano fatte richieste del tipo “individua il minimo/massimo dei valori per un attributo di tabella”, o di altra natura ma riconducibili a queste.
Questi esercizi sono risolvibili sfruttando un “self-join” seguito da una “differenza insiemistica”.
Nel documento viene spiegato qual’è il ragionamento logico che giustifica l’uso di queste due operazioni.

Si da per scontato che il lettore conosca bene l’algebra relazionale.
Si consideri la seguente tabella T d’esempio, definita come T(A) e con tuple:

+===+
| A |
+===+
| 1 |
| 2 |
| 3 |
| 4 |
+---+

La richiesta è: “Trova il valore minimo per A nella tabella T”.
Una semplice occhiata suggerisce che tale valore è 1, ma come si giunge a questo risultato con l’algebra relazionale?

Per risolvere questo tipo di problema, occorre aver presente i valori per A, come fossero un insieme S = {1, 2, 3, 4}, e quindi considerarne il prodotto cartesiano SxS:

SxS = {
    (1, 1),
    (1, 2),
    (1, 3),
    (1, 4),
    (2, 1),
    (2, 2),
    (2, 3),
    (2, 4),
    (3, 1),
    (3, 2),
    (3, 3),
    (3, 4),
    (4, 1),
    (4, 2),
    (4, 3),
    (4, 4)
}

Ora, si immagini di filtrare queste coppie rispetto alla condizione LT > RT (termine sinistro > termine destro); Il risultato di tale filtraggio è la seguente relazione R_>:

R_> = {
    (2, 1),
    (3, 1),
    (3, 2),
    (4, 1),
    (4, 2),
    (4, 3),
}

Ponendo l’attenzione sui diversi LT di R_>, si constata che non esiste alcuna coppia per cui LT = 1.
Il valore di interessa manca, ma è questo il punto, grazie a ciò, sarà possibile usare la sottrazione insiemistica.
Sia K = {2, 3, 4}, ovvero, l’insieme degli LT di R_>; Rimane solo da sottrarre K da S:

S - K = {1, 2, 3, 4} - {2, 3, 4} = {1}

Toh, è stato trovato il minimo!

In algebra relazionale, questo si traduce in un self-join (=theta join di T con una sua copia rinominata) di T, sottratto (differenza insiemistica) da T stesso.
Per soddisfare la richiesta, si utilizzano le seguenti operazioni:

K := π_{A}(T ⋈_{A > A'} ρ_{A'/A}(T));
r :=  T - K

Si noti che la notazione _{...} indica i contenuti (tra parentesi graffe) del pedice del particolare operatore.
Ad esempio, l’operazione ρ_{A'/A}(T) si legge come “rinomina l’attributo A di T in A’”, “A’/A” si trova nel pedice di ρ e T è fra parentesi tonde.

Qui r è il risultato desiderato.
Quando si ha un ordine tra valori (la cosiddetta “relazione d’ordine”), un self-join con una condizione del tipo LT op RT, seguito da una sottrazione insiemistica, di solito sono sufficienti per risolvere questa particulare classe di problemi .
L’operatore di comparazione op dipende strettamente dal risultato che si vuole ottenere.

Il self-join è quindi, l’equivalente relazionale del comparare tutti i singoli valori dell’insieme S tra loro; È ovvio che se si chiedono i valori maggiori, NON SI AVRÀ MAI il minimo in LT, perchè appunto, è l’unico valore che NON È MAGGIORE degli altri; Allo stesso modo, chiedendo tutti i valori minori, NON SI AVRÀ MAI il massimo in LT, perchè NON È MINORE degli altri.

Questo permette di capire perchè serve prima il self-join e poi la sottrazione: Il self-join ha lo scopo di accumulare i valori inutili come LT, cosicchè dopo, sia possibile sottrarli dalla tabella che fa da intero, per ottenere i valori di interesse come risultato.

Nel caso dell’individuazione del minimo/massimo, il ragionamento si riassume in:

  • Per individuare il minimo, prima usare il self-join con condizione LT > RT, quindi sottrarre il risultato dall’insieme degli LT;
  • Per individuare il massimo, prima usare il self-join con condizione LT < RT, quindi sottrarre il risultato dall’insieme degli LT;

Problema più complesso risolvibile mediante self-join + sottrazione

Qui segue la discussione di un problema più complesso, che può essere ricondotto al precedente ragionamento di risoluzione.
Si supponga che la base di dati presenti le seguenti tabelle con vincoli:

Azienda(ID, Nome);
Persona(ID, Nome, Sesso);
Impiegato(IDAzienda, IDPersona);

ID è chiave primaria per Azienda;
ID è chiave primaria per Persona;
La tupla di attributi (IDAzienda, IDPersona) è chiave primaria per Impiegato;
IDPersona fa riferimento a Persona(ID);
IDAzienda fa riferimento a Azienda(ID);

Viene quindi richiesto di “Restituire le aziende che impiegano solo donne”.
Questo significa che vanno escluse tutte le aziende che hanno una forza lavoro mista.
Sebbene non si tratti di un minimo/massimo, il ragionamento è lo stesso: Occorre dapprima usare un self-join, per prelevare l’insieme delle aziende con impiegati misti (ergo, le tuple inutili).
Per fare ciò, è possibile creare una vista v_1, definita come:

v_1 = π_{IDAzienda, Sesso}(Impiegato ⋈_{IDPersona = ID} Persona);

Con questa vista, abbiamo nella stessa tabella sia l’ID dell’azienda che il sesso dell’impiegato.
Ora tocca al self-join: Gli impiegati (soltanto i loro sessi, a dire il vero), appartenenti alla stessa azienda, devono essere combinati l’uno con l’altro, allo scopo di individuare le tuple, i cui due impiegati hanno sessi opposti.
Ciò funziona perchè, quando un impiegato viene combinato con i suoi colleghi, ed essi sono tutti dello stesso sesso, non accadrà mai che in una tupla, si avrà che il primo impiegato ha un sesso opposto rispetto al secondo.

Per ottenere questo risultato, occorre un self-join di v_1:

v_2 = π_{IDAzienda}(v_1 ⋈_{Sesso ≠ Sesso' ^ IDAzienda = IDAzienda'} ρ_{IDAzienda',Sesso'/IDAzienda,Sesso}(v_1))

v_2 raccoglie tutti gli ID di aziende che impiegano ambo i sessi, ovvero, le tuple inutili.
Ora si passa alla sottrazione insiemistica di v_2 da v_1, di modo da ottenere una vista v_3, contenente gli ID di aziende che hanno impiegati di un solo sesso:

v_3 = π_{IDAzienda}(v_1) - v_2

Non è ancora finita però, la richiesta vuole le aziende che impiegano soltanto donne.
Per superare quest’ostacolo, è sufficiente usare un theta join tra v_3 e v_1, di modo da escludere gli impiegati di sesso maschile.
L’operazione finale è quindi la seguente:

r = π_{IDAzienda}(v_3 ⋈ {Sesso ≠ 'M' ^ IDAzienda = IDAzienda'} ρ_{IDAzienda'/IDAzienda}(v_1))

r è il risultato desiderato, ovvero, l’insieme degli ID di aziende che impiegano solo donne.