Aller au contenu principal

Les arbres algébriques

Ce que ce chapitre apporte5 points
  • Comprendre pourquoi une algèbre est nécessaire entre SQL et le moteur.
  • Employer les opérations de base, et savoir ce que chacune fait aux lignes et aux colonnes.
  • Composer plusieurs opérations et lire l'arbre qui en résulte.
  • Traduire une requête SQL en expression algébrique, et l'inverse.
  • Réécrire un arbre en un arbre équivalent moins coûteux.

Quand on écrit une requête SQL, on dit ce qu'on veut ; le moteur, lui, décide comment l'obtenir. L'algèbre relationnelle est le langage dans lequel il raisonne, et l'arbre algébrique en est le dessin. Savoir le lire, c'est comprendre pourquoi deux requêtes qui donnent le même résultat ne coûtent pas le même prix.

Pourquoi une algèbre

SQL est un langage déclaratif. Une requête décrit le résultat voulu et ne dit rien de la marche à suivre. Le moteur, lui, doit bien choisir une marche à suivre, et il en existe des dizaines pour une même requête. Il lui faut donc un langage intermédiaire : assez formel pour être manipulé mécaniquement, assez proche des données pour que chaque opération soit exécutable. C'est l'algèbre relationnelle.

Cette algèbre repose sur une seule idée, et tout le reste en découle.

La propriété de clôture

Chaque opération prend une ou deux relations et rend une relation. Le résultat d'une opération est donc lui-même un opérande valide pour la suivante.

C'est ce qui permet d'emboîter les opérations sans limite, et c'est pourquoi une requête se dessine en arbre. Les feuilles sont les tables de la base, chaque nœud une opération, la racine le résultat final.

Un mot de vocabulaire, parce qu'il revient partout. Une relation est un ensemble de lignes ayant les mêmes attributs. Le mot ensemble n'est pas décoratif : en algèbre relationnelle, une relation ne contient pas deux fois la même ligne. SQL, lui, tolère les doublons, ce qui explique le mot-clé DISTINCT. Cette différence produit une surprise à la projection, on y revient plus bas.

La base de démonstration

Toutes les figures de ce chapitre travaillent sur la base employée par les chapitres SQL. Rien n'est inventé pour l'occasion : la même table Employe répond σ_{salaire > 2000}(Employe) ici et SELECT * FROM Employe WHERE salaire > 2000 là-bas, avec les mêmes lignes.

Cliquer sur un nœud pour voir la relation qu'il produit.

EmployeEmploye
Employe5 lignes · 6 attributs
idEmployenomprenomsalairedate_embaucheservice
1GirardNadia24502019-04-01Etudes
2LemoinePaul19802021-09-15Support
3BarbierSofia31002016-01-10Etudes
4RocheTom22002022-03-07Support
5VidalLucie27502018-11-23Commerce
La relation Employe, telle qu'elle est stockée. Cliquer sur le nœud pour l'afficher. En SQL : SELECT * FROM Employe

Les autres relations utilisées : Etudiant, Classe, Cours, Enseignant, Inscription, Client, Projet, TravailleSur.

Les opérations de base

Sélection (σ)

La sélection garde certaines lignes et ne touche pas aux colonnes. La condition s'écrit en indice, entre accolades. σ_{salaire > 2000}(Employe) se lit à voix haute : « les employés dont le salaire dépasse 2000 ».

Cliquer sur un nœud pour voir la relation qu'il produit.

σ salaire > 2000σ salaire > 2000EmployeEmploye
σ salaire > 20004 lignes · 6 attributs
idEmployenomprenomsalairedate_embaucheservice
1GirardNadia24502019-04-01Etudes
3BarbierSofia31002016-01-10Etudes
4RocheTom22002022-03-07Support
5VidalLucie27502018-11-23Commerce
La sélection filtre les lignes. Les six attributs sont conservés, quatre lignes sur cinq passent. En SQL : SELECT * FROM Employe WHERE salaire > 2000

Les conditions se combinent avec (et), (ou), ¬ (non). Les opérateurs de comparaison sont =, , <, >, , .

Cliquer sur un nœud pour voir la relation qu'il produit.

σ service = 'Etudes' ∧ salair…σ service = 'Etudes' ∧ salaire < 3000EmployeEmploye
σ service = 'Etudes' ∧ salaire < 30001 ligne · 6 attributs
idEmployenomprenomsalairedate_embaucheservice
1GirardNadia24502019-04-01Etudes
Le résultat ne contient qu'une ligne, Girard, avec ses six attributs. Barbier est bien aux Etudes, mais gagne 3100 : la seconde condition l'écarte. En SQL : SELECT * FROM Employe WHERE service = 'Etudes' AND salaire < 3000
Les NULL ne se comparent pas

Une comparaison dont un des membres vaut NULL n'est jamais vraie. Ni note = NULL, ni note ≠ NULL, ni note > 10. Une ligne dont l'attribut testé est absent est donc systématiquement écartée par une sélection.

Ce n'est pas un défaut de l'algèbre : NULL signifie « on ne sait pas », et on ne peut rien conclure d'une inconnue. SQL réserve pour cela une écriture à part, IS NULL.

Projection (π)

La projection garde certaines colonnes et ne filtre aucune ligne. Du moins en apparence. π_{nom, salaire}(Employe) se lit : « le nom et le salaire de chaque employé ».

Cliquer sur un nœud pour voir la relation qu'il produit.

π nom, salaireπ nom, salaireEmployeEmploye
π nom, salaire5 lignes · 2 attributs
nomsalaire
Girard2450
Lemoine1980
Barbier3100
Roche2200
Vidal2750
La projection ne garde que deux attributs sur six. Les cinq lignes subsistent. En SQL : SELECT nom, salaire FROM Employe

Voici la surprise annoncée plus haut. Une relation est un ensemble : elle ne peut pas contenir deux fois la même ligne. Si la projection fait disparaître ce qui distinguait deux lignes, celles-ci se confondent et le résultat en compte une seule.

Cliquer sur un nœud pour voir la relation qu'il produit.

π serviceπ serviceEmployeEmploye
π service3 lignes · 1 attribut
service
Etudes
Support
Commerce
Cinq employés, trois services. Deux paires de lignes sont devenues identiques et ont fusionné. En SQL : SELECT DISTINCT service FROM Employe
π correspond à SELECT DISTINCT, pas à SELECT

SELECT service FROM Employe rend cinq lignes en SQL, avec Etudes et Support deux fois chacune. La projection algébrique en rend trois.

La traduction fidèle de π est donc SELECT DISTINCT. C'est l'un des rares endroits où SQL s'écarte de l'algèbre dont il dérive. La raison est pratique : dédoublonner coûte cher, et le moteur ne le fait que si on le lui demande.

Renommage (ρ)

Le renommage change le nom d'un attribut sans toucher aux données. ρ_{salaireMensuel ← salaire}(Employe) se lit : « Employe, où l'attribut salaire s'appelle désormais salaireMensuel ». Il sert quand deux relations portent le même nom d'attribut et qu'il faut les distinguer.

Cliquer sur un nœud pour voir la relation qu'il produit.

ρ salaireMensuel ← salaireρ salaireMensuel ← salaireEmployeEmploye
ρ salaireMensuel ← salaire5 lignes · 6 attributs
idEmployenomprenomsalaireMensueldate_embaucheservice
1GirardNadia24502019-04-01Etudes
2LemoinePaul19802021-09-15Support
3BarbierSofia31002016-01-10Etudes
4RocheTom22002022-03-07Support
5VidalLucie27502018-11-23Commerce
Le résultat contient les mêmes cinq lignes et les mêmes six colonnes ; seul l'en-tête de la quatrième a changé. C'est une opération sur le schéma, pas sur les données.

Produit cartésien (×)

Le produit associe chaque ligne de gauche à chaque ligne de droite. Client × Projet se lit : « chaque client apparié à chaque projet, sans aucune condition ». C'est bien le problème : le résultat compte le produit des deux effectifs.

Cliquer sur un nœud pour voir la relation qu'il produit.

××ClientClientProjetProjet
×16 lignes · 7 attributs
Client.idClientnomprenomvilleidProjetlibelleProjet.idClient
1DurandPaulParis1Portail client1
1DurandPaulParis2Refonte ERP2
1DurandPaulParis3Application mobile3
1DurandPaulParis4Audit reseau4
2LeroySarahLyon1Portail client1
2LeroySarahLyon2Refonte ERP2
2LeroySarahLyon3Application mobile3
2LeroySarahLyon4Audit reseau4
3MoreauKarimParis1Portail client1
3MoreauKarimParis2Refonte ERP2
3MoreauKarimParis3Application mobile3
3MoreauKarimParis4Audit reseau4

4 lignes de plus, non affichées.

Quatre clients, quatre projets, seize lignes. Douze d'entre elles n'ont aucun sens. En SQL : SELECT * FROM Client CROSS JOIN Projet

Sur seize lignes, seules quatre associent un projet à son vrai client. Les douze autres sont du bruit. Le produit cartésien n'est presque jamais ce qu'on veut ; il est en revanche la brique dont la jointure est faite.

Jointure (⨝)

La jointure est un produit cartésien suivi d'une sélection. La condition s'écrit en indice, exactement comme pour σ. Client ⨝_{Client.idClient = Projet.idClient} Projet se lit : « chaque client apparié aux projets qui portent son identifiant ».

Cliquer sur un nœud pour voir la relation qu'il produit.

⨝ Client.idClient = Projet.id…⨝ Client.idClient = Projet.idClientClientClientProjetProjet
⨝ Client.idClient = Projet.idClient4 lignes · 7 attributs
Client.idClientnomprenomvilleidProjetlibelleProjet.idClient
1DurandPaulParis1Portail client1
2LeroySarahLyon2Refonte ERP2
3MoreauKarimParis3Application mobile3
4FontaineEvaBordeaux4Audit reseau4
La même opération que ci-dessus, mais filtrée : les quatre lignes qui ont un sens. En SQL : SELECT * FROM Client JOIN Projet ON Client.idClient = Projet.idClient

Écrite sans condition, la jointure devient naturelle : elle apparie sur tous les attributs qui portent le même nom des deux côtés, et ne garde qu'un exemplaire de ces attributs.

Cliquer sur un nœud pour voir la relation qu'il produit.

⨝ naturelle⨝ naturelleClientClientProjetProjet
⨝ naturelle4 lignes · 6 attributs
idClientnomprenomvilleidProjetlibelle
1DurandPaulParis1Portail client
2LeroySarahLyon2Refonte ERP
3MoreauKarimParis3Application mobile
4FontaineEvaBordeaux4Audit reseau
Le résultat contient les quatre mêmes lignes que ci-dessus, mais six colonnes au lieu de sept : l'appariement se fait sur idClient, le seul nom commun, et idClient n'est gardé qu'une fois.
Commode, mais fragile

La jointure naturelle dépend entièrement des noms de colonnes. Ajouter demain une colonne dateCreation aux deux tables changerait silencieusement l'appariement, et donc le résultat, sans qu'aucune requête ait été modifiée.

En pratique, écrire la condition explicitement coûte une ligne et évite cette classe de surprise.

Opérations ensemblistes (∪, ∩, −)

L'union, l'intersection et la différence se comportent comme sur des ensembles ordinaires. Elles exigent que les deux relations aient le même schéma : même nombre d'attributs, dans le même ordre, de mêmes types.

Les trois se lisent en une phrase chacune. Inscrits ∪ Boursiers : « les noms qui figurent dans au moins une des deux listes ». Inscrits ∩ Boursiers : « ceux qui figurent dans les deux ». Inscrits − Boursiers : « ceux qui figurent dans la première et pas dans la seconde ».

Cliquer sur un nœud pour voir la relation qu'il produit.

InscritsInscritsBoursiersBoursiers
4 lignes · 1 attribut
nom
Martin
Diallo
Dupond
Nguyen
Le résultat contient quatre lignes pour cinq lignes en entrée. Diallo figure dans les deux listes et n'apparaît qu'une fois.

Cliquer sur un nœud pour voir la relation qu'il produit.

InscritsInscritsBoursiersBoursiers
1 ligne · 1 attribut
nom
Diallo
Le résultat ne contient qu'une ligne, Diallo : le seul nom présent des deux côtés.

Cliquer sur un nœud pour voir la relation qu'il produit.

InscritsInscritsBoursiersBoursiers
2 lignes · 1 attribut
nom
Martin
Dupond
Le résultat contient deux lignes, Martin et Dupond. Attention à l'ordre : Boursiers − Inscrits rendrait Nguyen, et lui seul.

La différence est l'opération qui permet d'exprimer une négation. « Les cours sans enseignant affecté » se traduit par une différence entre tous les cours et ceux qui en ont un.

Cliquer sur un nœud pour voir la relation qu'il produit.

π intituleπ intituleCoursCoursπ intituleπ intituleσ idEnseignant > 0σ idEnseignant > 0CoursCours
2 lignes · 1 attribut
intitule
Physique
Reseaux
Le résultat contient deux lignes, les deux cours sans enseignant. Une comparaison ne retient jamais un NULL : ces deux cours sortent de la seconde projection, qui en compte cinq sur sept, et restent donc dans la différence.

Division (÷)

Le besoin apparaît dès qu'une question contient « tous les » : tous les projets d'une liste, toutes les compétences exigées, tous les cours d'un programme. Aucune des opérations précédentes ne sait y répondre. Une jointure répond à « au moins un », ce qui est une question différente et plus large.

La division prend une relation à deux attributs, le dividende, et une relation à un attribut, le diviseur. Elle rend les valeurs de gauche associées à toutes celles de droite. Affectation ÷ Tous se lit : « les employés qui travaillent sur tous les projets listés dans Tous ».

Six affectations d'un côté, deux projets exigés de l'autre.

Cliquer sur un nœud pour voir la relation qu'il produit.

÷÷AffectationAffectationTousTous
÷2 lignes · 1 attribut
employe
Nadia
Sofia
Le résultat contient deux lignes et une seule colonne, employe : Nadia et Sofia. Cliquer sur les deux feuilles pour retrouver les données du calcul détaillé ci-dessous.

Le calcul se fait employé par employé. Pour chacun, rassembler ses projets, puis vérifier que les deux projets exigés y figurent tous les deux. C'est le tableau intermédiaire que la notation cache.

employéses projetsPortail ?Mobile ?retenu ?
NadiaPortail, Mobileouiouioui
PaulPortailouinonnon
SofiaPortail, Mobile, ERPouiouioui

Trois employés examinés, deux retenus. Deux enseignements se lisent dans ce tableau. Paul est écarté par une seule case à « non » : il suffit qu'un seul élément du diviseur manque. Sofia est retenue malgré ERP, qui n'est pas dans le diviseur : la division exige au moins les projets listés, pas exactement ceux-là.

Noter enfin que la colonne projet a disparu du résultat, qui ne porte plus que employe. C'est la règle : la division retire les attributs du diviseur et ne garde que les autres.

C'est l'opération que personne ne retient, parce qu'elle est la seule à ne pas avoir de mot-clé SQL. Elle s'écrit avec une double négation : les employés pour lesquels il n'existe pas de projet de la liste auquel ils ne participent pas. Cette formulation lourde est justement ce que la division résume en un symbole.

Le tableau récapitulatif

OpérationNotationEffet sur les lignesEffet sur les colonnes
Sélectionσ_{condition}(R)en garde certainesinchangées
Projectionπ_{attributs}(R)dédoublonnen'en garde que certaines
Renommageρ_{b ← a}(R)inchangéesen renomme une
ProduitR × Smultiplie les effectifsconcatène les deux schémas
JointureR ⨝_{condition} Sapparieconcatène les deux schémas
UnionR ∪ Srassemble, dédoublonneschéma commun exigé
IntersectionR ∩ Sgarde le communschéma commun exigé
DifférenceR − Sretireschéma commun exigé
DivisionR ÷ Sgarde ce qui couvre tout Sretire les attributs de S

Composer, et donc dessiner

La clôture permet d'emboîter. L'expression suivante enchaîne trois opérations : une jointure, une sélection, une projection. Elle se lit de l'intérieur vers l'extérieur : « apparier chaque étudiant à sa classe, ne garder que la classe 1A, puis n'afficher que le nom et le prénom ».

Cliquer sur un nœud pour voir la relation qu'il produit.

π nom, prenomπ nom, prenomσ nomClasse = '1A'σ nomClasse = '1A'⨝ Etudiant.idClasse = Classe…⨝ Etudiant.idClasse = Classe.idClasseEtudiantEtudiantClasseClasse
π nom, prenom4 lignes · 2 attributs
nomprenom
MartinLea
DialloAmine
DupondSophie
PetitSofia
Le résultat contient quatre lignes et deux colonnes, nom et prenom. Cliquer sur chaque nœud pour suivre la transformation, de la table brute au résultat. En SQL : SELECT nom, prenom FROM Etudiant JOIN Classe ON Etudiant.idClasse = Classe.idClasse WHERE nomClasse = '1A'

L'arbre se lit de bas en haut. Les feuilles sont les tables telles qu'elles sont stockées ; chaque nœud consomme le résultat du niveau inférieur ; la racine porte le résultat final.

ÉlémentRôle
Feuillesles tables de la base
Nœuds internesles opérations, σ π ρ ⨝ × ∪ ∩ − ÷
Arêtesl'ordre d'évaluation, du bas vers le haut
Racinele résultat rendu

Cet arbre est aussi la façon dont un moteur représente une requête avant de l'exécuter. Le lire, c'est lire le plan d'exécution. Et si le moteur se donne la peine de choisir entre plusieurs arbres, c'est qu'ils ne se valent pas.

Optimiser : pousser les sélections vers les feuilles

Voici la raison pour laquelle ce chapitre existe. Deux arbres peuvent rendre exactement le même résultat en faisant un travail très différent. Le nombre de lignes affiché à chaque nœud le montre directement.

Reprenons la question : le nom et le prénom des étudiants de la classe 1A. Première écriture, la plus naïve : tout combiner, puis trier le tas.

Cliquer sur un nœud pour voir la relation qu'il produit.

π nom, prenomπ nom, prenomσ Etudiant.idClasse = Classe…σ Etudiant.idClasse = Classe.idClasse ∧ nomClasse = '1A'××EtudiantEtudiantClasseClasse
π nom, prenom4 lignes · 2 attributs
nomprenom
MartinLea
DialloAmine
DupondSophie
PetitSofia
Version 1. Le produit cartésien fabrique 30 lignes, dont 26 seront jetées à l'étape suivante.

Deuxième écriture : remplacer le produit par une jointure, ce qui revient à effectuer une partie du filtrage plus tôt.

Cliquer sur un nœud pour voir la relation qu'il produit.

π nom, prenomπ nom, prenomσ nomClasse = '1A'σ nomClasse = '1A'⨝ Etudiant.idClasse = Classe…⨝ Etudiant.idClasse = Classe.idClasseEtudiantEtudiantClasseClasse
π nom, prenom4 lignes · 2 attributs
nomprenom
MartinLea
DialloAmine
DupondSophie
PetitSofia
Version 2. La jointure ne construit plus que 10 lignes. Le nœud le plus large est passé de 30 à 10.

Troisième écriture : filtrer Classe avant de joindre. La sélection descend jusqu'à la feuille.

Cliquer sur un nœud pour voir la relation qu'il produit.

π nom, prenomπ nom, prenom⨝ Etudiant.idClasse = Classe…⨝ Etudiant.idClasse = Classe.idClasseEtudiantEtudiantσ nomClasse = '1A'σ nomClasse = '1A'ClasseClasse
π nom, prenom4 lignes · 2 attributs
nomprenom
MartinLea
DialloAmine
DupondSophie
PetitSofia
Version 3. La classe est filtrée d'abord : la jointure ne voit qu'une seule ligne à droite et n'en produit que 4, soit exactement le résultat. Plus rien n'est construit pour être jeté.

Les trois arbres rendent les quatre mêmes lignes. Cliquer sur le nœud le plus coûteux de chacun donne 30, puis 10, puis 4 : la troisième version ne fabrique aucune ligne intermédiaire inutile. Sur une base de démonstration, la différence de temps est invisible ; sur dix mille étudiants et cent classes, la version 1 construit un million de lignes pour en garder quelques dizaines.

Les règles de réécriture
  1. Descendre les sélections le plus près possible des feuilles. Filtrer avant de combiner est toujours au moins aussi bon.
  2. Remplacer produit puis sélection par une jointure. C'est la même opération, mais le moteur peut l'exécuter sans matérialiser le produit.
  3. Descendre les projections, en gardant les attributs dont les opérations supérieures ont besoin. Moins de colonnes transportées, moins de mémoire.
  4. Commencer par la jointure la plus sélective quand il y en a plusieurs, pour que les suivantes travaillent sur peu de lignes.

Toutes ces règles disent la même chose : jeter tôt ce qui sera jeté de toute façon.

Un arbre à plusieurs jointures

Les trois versions précédentes n'avaient que deux tables. Le gain devient beaucoup plus net dès qu'il y en a davantage, puisque chaque jointure travaille sur ce que la précédente lui a laissé.

Objectif : le nom des étudiants inscrits à un cours dispensé par l'enseignante Durand. Quatre tables, trois jointures.

Cliquer sur un nœud pour voir la relation qu'il produit.

π nomπ nom⨝ Etudiant.idEtudiant = Inscr…⨝ Etudiant.idEtudiant = Inscription.idEtudiantEtudiantEtudiant⨝ Inscription.idCours = Cours…⨝ Inscription.idCours = Cours.idCoursInscriptionInscription⨝ Cours.idEnseignant = Enseig…⨝ Cours.idEnseignant = Enseignant.idEnseignantCoursCoursσ nomEns = 'Durand'σ nomEns = 'Durand'EnseignantEnseignant
π nom7 lignes · 1 attribut
nom
Martin
Diallo
Dupond
Lefebvre
Moreau
Marchand
Nguyen
Le résultat contient sept noms dans une seule colonne. La sélection sur l'enseignante est placée au plus bas : cliquer sur les nœuds de bas en haut pour voir la relation se construire, puis se réduire. En SQL : SELECT DISTINCT e.nom FROM Etudiant e JOIN Inscription i ON e.idEtudiant = i.idEtudiant JOIN Cours c ON i.idCours = c.idCours JOIN Enseignant en ON c.idEnseignant = en.idEnseignant WHERE en.nomEns = 'Durand'

Remarquer la projection finale : sept lignes contre huit pour la jointure qu'elle surmonte. Un étudiant inscrit à deux cours de Durand y figurait deux fois, et le dédoublonnage les confond.

La méthode

  1. Lire la question en français et repérer les mots qui portent une opération : « seulement ceux qui… » appelle une sélection, « le nom de… » une projection, « et leur… » une jointure, « tous les… » une division.
  2. Lister les tables nécessaires, et elles seules. Ce sont les feuilles de l'arbre. Une table qui n'apporte ni attribut au résultat ni attribut à une condition est de trop.
  3. Écrire les jointures, chacune avec sa condition d'appariement explicite, en partant de la table qui porte l'information demandée.
  4. Placer chaque sélection sur la feuille dont elle dépend. Une condition qui ne porte que sur une table descend jusqu'à cette table ; une condition qui mélange deux tables reste au-dessus de leur jointure.
  5. Projeter en dernier les seuls attributs demandés, en se rappelant que la projection dédoublonne.
  6. Relire l'expression de bas en haut et la dire en français. Si la phrase obtenue n'est pas la question de départ, l'arbre est faux.
  7. Chercher le nœud le plus large et se demander ce qui le ferait rétrécir : une sélection descendue plus bas, une projection avancée, un produit remplacé par une jointure.
  8. Contrôler sur trois ou quatre lignes de chaque table, à la main, le nombre de lignes que l'arbre devrait rendre, puis le comparer à ce qu'il rend.

Synthèse

  • SQL dit le résultat voulu et jamais la marche à suivre. Le moteur a donc besoin d'un langage intermédiaire, assez formel pour être manipulé mécaniquement et assez proche des données pour être exécuté : c'est l'algèbre relationnelle.
  • Chaque opération prend une ou deux relations et rend une relation. Cette clôture permet de les emboîter sans limite, et donne à toute requête la forme d'un arbre.
  • σ garde des lignes et laisse les colonnes intactes. π garde des colonnes et peut réduire les lignes, parce qu'une relation est un ensemble et ne contient jamais deux fois la même ligne.
  • ρ ne change qu'un nom d'attribut. × associe chaque ligne de gauche à chaque ligne de droite. ⨝ est ce même produit, filtré par une condition d'appariement.
  • ∪, ∩ et − exigent deux relations de même schéma, et seule − dépend de l'ordre des opérandes. C'est − qui exprime une négation, faute d'opérateur disant « sans ».
  • ÷ est le seul opérateur qui réponde à une question en « tous les ». Il retire du résultat les attributs du diviseur, et ne disqualifie pas celui qui en possède davantage.
  • La traduction vers SQL est directe pour σ (WHERE), ⨝ (JOIN … ON) et × (CROSS JOIN). π demande SELECT DISTINCT, et ÷ n'a pas de mot-clé : elle s'écrit en double négation.
  • Un arbre se lit de bas en haut : les feuilles sont les tables stockées, chaque nœud consomme le niveau inférieur, la racine porte le résultat rendu.
  • Deux arbres équivalents ne coûtent pas le même prix, et ce prix se lit au nombre de lignes du nœud le plus large, jamais au résultat final.
  • Réécrire un arbre revient toujours à jeter tôt ce qui sera jeté de toute façon : descendre les sélections vers les feuilles, descendre les projections, remplacer un produit suivi d'une sélection par une jointure, commencer par la jointure la plus sélective.

Quiz

Vérification rapideon peut se reprendre

1.Que rend π_{ville}(Etudiant), sachant que la table contient dix étudiants répartis sur quatre villes ?

2.Quelle écriture SQL traduit fidèlement la projection algébrique ?

3.Une jointure R ⨝ S sous condition équivaut à :

4.Deux tables de 200 et 50 lignes. Combien de lignes compte leur produit cartésien ?

5.Où placer une sélection pour qu'un arbre coûte le moins cher possible ?

6.σ_{note > 10}(Inscription) retient-elle les lignes dont la note vaut NULL ?

7.Quelle condition R et S doivent-elles remplir pour que R ∪ S ait un sens ?

8.EmployeCompetence ÷ Competence rend :

9.Trois arbres rendent le même résultat, avec un nœud le plus large à 30, 10 et 4 lignes. Lequel choisir ?

Exercices

Traduire en algèbre : les employés du service Support gagnant plus de 2000

Le filtre porte sur deux attributs, aucune colonne n'est demandée en particulier.

Cliquer sur un nœud pour voir la relation qu'il produit.

σ service = 'Support' ∧ salai…σ service = 'Support' ∧ salaire > 2000EmployeEmploye
σ service = 'Support' ∧ salaire > 20001 ligne · 6 attributs
idEmployenomprenomsalairedate_embaucheservice
4RocheTom22002022-03-07Support
Roche passe, Lemoine non : 1980 est inférieur à 2000. En SQL : SELECT * FROM Employe WHERE service = 'Support' AND salaire > 2000
Traduire en algèbre : les villes où résident des étudiants, sans répétition

Une seule colonne demandée, et le dédoublonnage vient gratuitement avec la projection.

Cliquer sur un nœud pour voir la relation qu'il produit.

π villeπ villeEtudiantEtudiant
π ville4 lignes · 1 attribut
ville
Paris
Lyon
Nantes
Bordeaux
Quatre villes pour dix étudiants. En SQL : SELECT DISTINCT ville FROM Etudiant
Traduire en algèbre : le nom des étudiants de Paris et leur classe

Une jointure est nécessaire, puisque le nom de la classe est dans une autre table que l'étudiant. La sélection sur la ville porte sur Etudiant, elle peut donc descendre jusqu'à cette feuille.

Cliquer sur un nœud pour voir la relation qu'il produit.

π nom, nomClasseπ nom, nomClasse⨝ Etudiant.idClasse = Classe…⨝ Etudiant.idClasse = Classe.idClasseσ ville = 'Paris'σ ville = 'Paris'EtudiantEtudiantClasseClasse
π nom, nomClasse4 lignes · 2 attributs
nomnomClasse
Martin1A
Dupond1A
Bernard1B
Moreau2A
La sélection descend sous la jointure : celle-ci ne travaille que sur les quatre Parisiens. En SQL : SELECT nom, nomClasse FROM Etudiant JOIN Classe ON Etudiant.idClasse = Classe.idClasse WHERE ville = 'Paris'
Traduire en algèbre : les cours de plus de 24 heures et le nom de leur enseignant

Deux tables, une sélection, une projection sur deux attributs venus de tables différentes.

Cliquer sur un nœud pour voir la relation qu'il produit.

π intitule, nomEnsπ intitule, nomEns⨝ Cours.idEnseignant = Enseig…⨝ Cours.idEnseignant = Enseignant.idEnseignantσ volumeHoraire > 24σ volumeHoraire > 24CoursCoursEnseignantEnseignant
π intitule, nomEns3 lignes · 2 attributs
intitulenomEns
Bases de donneesDurand
MathematiquesAubert
InformatiqueDurand
Quatre cours dépassent 24 heures, mais Physique n'a pas d'enseignant : la jointure l'écarte et il en reste trois. En SQL : SELECT intitule, nomEns FROM Cours JOIN Enseignant ON Cours.idEnseignant = Enseignant.idEnseignant WHERE volumeHoraire > 24

Noter ce que la jointure fait ici en plus du filtre : Physique fait 27 heures et disparaît quand même, parce que son idEnseignant est NULL et qu'aucune ligne de Enseignant ne lui correspond.

Lire l'arbre suivant et en donner la requête SQL

Cliquer sur un nœud pour voir la relation qu'il produit.

π prenom, nomπ prenom, nomσ actif = 0σ actif = 0EtudiantEtudiant
π prenom, nom2 lignes · 2 attributs
prenomnom
TomDurand
SofiaPetit
Que fait cet arbre ?

De bas en haut : partir de Etudiant, ne garder que les lignes dont actif vaut 0, puis n'afficher que le prénom et le nom.

requete.sql
Résultat
>_ Prêt à exécuter…

Le DISTINCT traduit la projection. Ici il ne change rien, les deux étudiants concernés ayant des noms différents, mais l'écriture reste la traduction fidèle.

Améliorer cet arbre

L'expression suivante donne le bon résultat, mais fait travailler la jointure pour rien.

Cliquer sur un nœud pour voir la relation qu'il produit.

π nomπ nomσ ville = 'Lyon'σ ville = 'Lyon'⨝ Etudiant.idClasse = Classe…⨝ Etudiant.idClasse = Classe.idClasseEtudiantEtudiantClasseClasse
π nom3 lignes · 1 attribut
nom
Diallo
Lefebvre
Nguyen
Avant. La jointure produit 10 lignes, la sélection en garde 3.

La condition ville = 'Lyon' ne porte que sur Etudiant. Rien n'oblige à attendre la jointure pour l'appliquer.

Cliquer sur un nœud pour voir la relation qu'il produit.

π nomπ nom⨝ Etudiant.idClasse = Classe…⨝ Etudiant.idClasse = Classe.idClasseσ ville = 'Lyon'σ ville = 'Lyon'EtudiantEtudiantClasseClasse
π nom3 lignes · 1 attribut
nom
Diallo
Lefebvre
Nguyen
Après. La sélection descend sur la feuille Etudiant : la jointure ne voit plus que 3 lignes à gauche au lieu de 10.

Le résultat est identique. Le travail intermédiaire est divisé par trois sur cette petite base, et dans le même rapport que la sélectivité du filtre sur une grande.

Exprimer une négation : les clients sans projet

Aucun opérateur ne dit « sans ». Il faut passer par une différence : tous les clients, moins ceux qui ont un projet.

Cliquer sur un nœud pour voir la relation qu'il produit.

π idClientπ idClientClientClientπ idClientπ idClientProjetProjet
0 ligne · 1 attribut

Relation vide : aucune ligne ne satisfait cette opération. Les attributs sont pourtant définis (idClient), ce qui n'est pas la même chose qu'une erreur.

Résultat vide : dans cette base, les quatre clients ont chacun un projet.

Une relation vide n'est pas une erreur. Elle répond que personne ne satisfait la condition, ce qui est une réponse.

Pour obtenir un résultat non vide, ajouter mentalement un cinquième client sans projet : il serait le seul à figurer dans la différence.

Une question en « tous les » : les employés affectés à tous les projets

C'est une division. Le dividende doit être réduit aux deux attributs concernés, sans quoi la colonne heures ferait partie de la clé.

Cliquer sur un nœud pour voir la relation qu'il produit.

÷÷π idEmploye, idProjetπ idEmploye, idProjetTravailleSurTravailleSurπ idProjetπ idProjetProjetProjet
÷0 ligne · 1 attribut

Relation vide : aucune ligne ne satisfait cette opération. Les attributs sont pourtant définis (idEmploye), ce qui n'est pas la même chose qu'une erreur.

Résultat vide : aucun employé ne travaille sur les quatre projets à la fois.

En réduisant l'exigence à deux projets, quelqu'un apparaît.

Cliquer sur un nœud pour voir la relation qu'il produit.

÷÷π idEmploye, idProjetπ idEmploye, idProjetTravailleSurTravailleSurπ idProjetπ idProjetσ idProjet = 2 ∨ idProjet = 3σ idProjet = 2 ∨ idProjet = 3ProjetProjet
÷1 ligne · 1 attribut
idEmploye
3
L'employée 3 est la seule affectée à la fois au projet 2 et au projet 3.
Pourquoi π_{nom}(Etudiant × Client) échoue-t-il ?

Parce que nom désigne deux colonnes différentes après le produit : celle de Etudiant et celle de Client. L'expression est ambiguë, et le moteur le signale plutôt que de choisir au hasard.

Deux façons de lever l'ambiguïté. Qualifier l'attribut :

Cliquer sur un nœud pour voir la relation qu'il produit.

π Etudiant.nomπ Etudiant.nom××EtudiantEtudiantClientClient
π Etudiant.nom10 lignes · 1 attribut
nom
Martin
Diallo
Dupond
Durand
Lefebvre
Bernard
Moreau
Marchand
Nguyen
Petit
Le résultat contient dix noms, un par étudiant. Le produit en fabriquait quarante lignes ; la projection les dédoublonne.

Ou renommer avant de combiner, ce qui vaut mieux quand l'expression est longue :

Cliquer sur un nœud pour voir la relation qu'il produit.

π nomEtudiantπ nomEtudiant××ρ nomEtudiant ← nomρ nomEtudiant ← nomEtudiantEtudiantClientClient
π nomEtudiant10 lignes · 1 attribut
nomEtudiant
Martin
Diallo
Dupond
Durand
Lefebvre
Bernard
Moreau
Marchand
Nguyen
Petit
Même résultat, les mêmes dix noms. Le renommage règle le conflit à la source plutôt qu'au moment de projeter.

Ressources complémentaires

Mettre en pratique