Opération DIVISION

Il n'existe pas en SQL d'équivalent direct à la division.

Cependant il est toujours possible de trouver une autre solution, notamment par l'intermédiaire des opérations de calcul et de regroupement.

Dans l'exemple présenté, on souhaite trouver les athlètes qui participent à toutes les épreuves. En algèbre relationnelle une autre solution que la division pourrait être :

N=CALCULER(EPREUVE, Comptage())
R1=REGROUPER_ET_CALCULER(PARTICIPER, Athlète, Nb:Comptage())
R2=SELECTION(R1, Nb=N)
R3=PROJECTION(R2, Athlète)

et en SQL :

SELECT Athlète FROM PARTICIPER
GROUP BY Athlète
HAVING COUNT(*) = (SELECT COUNT(*) FROM EPREUVE) ;

On pourra trouver cette solution imparfaite par rapport aux solutions plus "propres" généralement données pour la division, solutions souvent basées sur une double négation et mettant en oeuvre plusieurs SELECT imbriqués et corrélés (très coûteux en temps d'exécution). Approfondissons les choses avec un autre jeu d'essai volontairement plus contraignant :


PARTICIPER EPREUVE
Athlète Epreuve Epreuve
Dupont 200 m 200 m
Dupont 400 m 400 m
Dupont 110 m H 110 m H
Dupont 100 m 200 m
Martin 200 m
Martin 400 m
Martin 110 m H
Bertrand 200 m
Bertrand 400 m
Michel 200 m

On peut alors vouloir obtenir :
1) les athlètes ayant participé au moins à toutes les épreuves de la table EPREUVE (Dupont et Martin)
2) les athlètes qui ont participé uniquement aux épreuves de la table EPREUVE et à aucune autre (Division "exacte"), ici Martin.

Dans le 1er cas, la solution n'est guère plus compliquée :

SELECT Athlète FROM PARTICIPER
WHERE Epreuve IN (SELECT Epreuve FROM EPREUVE)
GROUP BY Athlète
HAVING COUNT(*) = (SELECT COUNT(DISTINCT Epreuve) FROM EPREUVE) ;

Dans le 2ème cas, il est encore possible de répondre assez "simplement" à la question sur la base d'une jointure externe (SQL2) et en utilisant la particularité des fonctions d'agrégation d'ignorer les valeurs nulles. Voici cette solution :

SELECT Athlète
FROM PARTICIPER A LEFT JOIN (SELECT DISTINCT Epreuve FROM EPREUVE) B ON A.Epreuve = B.Epreuve
GROUP BY Athlète
HAVING COUNT(*) = (SELECT COUNT(DISTINCT Epreuve) FROM EPREUVE)
AND COUNT(B.Epreuve) =(SELECT COUNT(DISTINCT Epreuve) FROM EPREUVE) ;

Pour mieux comprendre, voici le résultat renvoyé par la jointure externe gauche (LEFT JOIN) entre PARTICIPER et EPREUVE :

Athlète A.Epreuve B.Epreuve
Dupont 200 m 200 m
Dupont 400 m 400 m
Dupont 110 m H 110 m H
Dupont 100 m NULL
Martin 200 m 200 m
Martin 400 m 400 m
Martin 110 m H 110 m H
Bertrand 400 m 400 m
Bertrand 200 m 200 m
Michel 200 m 200 m

Ensuite le regroupement avec comptage renvoie :

Athlète COUNT(*) COUNT(B.Epreuve)
Dupont 4 3
Martin 3 3
Bertrand 2 2
Michel 1 1

Seul Martin a les 2 résultats égaux au nombre d'épreuves différentes (et vérifie donc la condition exprimée derrière le HAVING). CQFD !

Je vous laisse comparer avec l'autre type de solution (plus élégante) basée sur la double négation et les SELECT imbriqués et corrélés :

SELECT Athlète FROM PARTICIPER A
WHERE NOT EXISTS (SELECT * FROM EPREUVE
                                            WHERE NOT EXISTS (SELECT * FROM PARTICIPER B
                                                                                        WHERE (A.Athlète = B.Athlète)
                                                                                             AND (B.Epreuve = EPREUVE.Epreuve)))
GROUP BY Athlète
HAVING COUNT(*) = (SELECT COUNT (DISTINCT Epreuve) FROM EPREUVE) ;