3. Sélection sur plusieurs tables

Une base de données ne comporte que très rarement une unique table. En général, les données sont réparties sur de nombreuses tables, et extraire ce que l'on veut dans un format intelligible réclame des requêtes complexes, et parfois coûteuses. La notion que nous présentons à présent dans ce chapitre introductif est la notion de jointure. Établir une jointure entre deux tables consiste à croiser les informations portées par chacune de ces tables, suivant des critères très précis. Bien maîtriser cette notion est indispensable en SQL.

3.1. Ajout du lieu de naissance

Continuons sur notre exemple, en ajoutant à notre table, la commune où sont nés nos marins.

Tableau 2. Lieux de naissance

Nom Prénom Lieu de naissance
Auguin Christophe Granville
Bart Jean Dunkerque
Cartier Jacques Saint Malo
Dumont d'Urville Jules Condé
Dupleix Joseph Landrecies
Moitessier Bernard Saïgon
Montcalm Louis Nîmes
Surcouf Robert Saint Malo
Tabarly Eric Nantes

L'approche naïve consisterait à ajouter une colonne à notre table, de type varchar, et à renseigner cette colonne avec ces nouvelles données. Cette approche pourrait certainement fonctionner, mais elle poserait d'emblée un problème. Dans notre exemple très simple, sur 9 marins, nous en avons déjà 2 qui sont nés au même endroit : Saint Malo. On peut supposer que si l'on avait des milliers de marins dans notre table, le nombre de doublons dans cette colonne serait assez important. Le traitement d'une requête du type "je veux extraire tous les marins nés à Saint Malo" fonctionnerait de façon triviale, en sélectionnant toutes les valeurs "Saint Malo" dans la colonne "lieu de naissance". Il vaut mieux que la saisie des valeurs de cette colonne se passe sans erreur, car si la valeur "Saint Malau" a été saisie à la place de "Saint Malo", le marin associé ne sera jamais sélectionné par une telle requête ! Pire que cela, détecter ce genre d'erreurs sur de très grands volumes de données peut devenir un réel problème : il se peut que notre marin soit perdu à jamais dans l'immensité océanique des bases de donneés mal rangées. De plus, enregistrer une commune dans une table Marins n'est pas une façon naturelle de traiter un tel problème. Après tout, une commune existe indépendamment du fait qu'un marin y est né. Une bonne façon de traiter ce problème, est de reconnaître l'indépendance de ces communes, et de leur offrir leur propre table. Afin de pouvoir référencer ces communes, on ajoute à cette table une colonne ID, qui portera un numéro, unique dans cette table, et qui nous servira de clé de référence. Créons donc la table Communes suivante.

Exemple 14. Création de la table Communes

create  table Communes(
   id  int,
   nom,  varchar(32)
) ;

Tableau 3. Lieux de naissance

ID Nom de commune
1 Condé
2 Dunkerque
3 Landrecies
4 Nantes
5 Nîmes
6 Saïgon
7 Saint Malo
8 Granville

Une fois cette table créée et renseignée, il nous faut ajouter une colonne à la table Marins.

Exemple 15. Ajout de la colonne id_commune

alter  table Marins  add (id_commune  int) ;

Il ne nous reste plus qu'à renseigner la table Marins avec les valeurs suivantes.

Tableau 4. Table Marins avec les lieux de naissance

Nom Prénom Lieu de naissance
Auguin Christophe 8
Bart Jean 2
Cartier Jacques 7
Dumont d'Urville Jules 1
Dupleix Joseph 3
Moitessier Bernard 6
Montcalm Louis 5
Surcouf Robert 7
Tabarly Eric 4

Une fois nos tables mises à jour, on peut écrire une requête sur la table Marins et la table Communes, qui nous affiche le nom de chaque marin et sa commune de naissance.

Exemple 16. Requête jointe sur Marins et Communes

select Marins.nom, Marins.prenom, Communes.nom
    from Marins
    join Communes  on Marins.id_commune = Communes.id
    order  by Marins.nom ;

Le résultat est bien sûr le suivant.
NOM                      PRENOM                      NOM
----------------------- --------------------------- ---------------
Auguin                   Christophe                  Granville
Bart                     Jean                        Dunkerque
Cartier                  Jacques                     St Malo
Dumont d'Urville         Jules                       Condé
Dupleix                  Joseph                      Landrecies
Moitessier               Bernard                     Saïgon
Montcalm                 Louis                       Nîmes
Surcouf                  Robert                      Saint Malo
Tabarly                  Eric                        Nantes
Détaillons ici les nouveautés de cette requête.
  • Chaque champ est précédé du nom de la table à laquelle il appartient. Cela permet de lever les ambigüités dans le cas où deux tables ont des champs de même nom. On aurait pu l’omettre pour le champ Marins.prenom, puisqu’il n’y a qu’un seul champ prenom dans les deux tables.
  • Le mot-clé from est suivi du nom de la table principale de la requête : Marins.
  • La table Communes est jointe à la table Marin par le mot-clé on. La jointure est réglée par l’égalité entre les champs id et id_commune des tables Communes et Marins respectivement
Formellement, une jointure entre deux tables est un produit cartésien entre ces deux tables. Le produit cartésien entre un ensemble A et un ensemble B est l’ensemble de tous les couples formés d’un élément de A et d’un élément de B. Le nombre de lignes de la table jointe est donc le produit du nombre de lignes des tables initiales. La sélection des lignes dans la table jointe se fait ensuite de façon classique, comme s’il s’agissait d’une table normale. Bien sûr, cette table jointe est juste une façon de raisonner. En aucun cas le serveur ne la construit : si c’était le cas, il aurait à gérer des tables de taille énorme, même pour des petites requêtes. Notons l'ancienne syntaxe SQL pour écrire cette jointure. Il faut clairement lui préférer la nouvelle (d'ailleurs pas si nouvelle que cela...) syntaxe ci-dessus. Elle nous permettra de gérer les valeurs nulles de façon explicite, ce qui n'est pas le cas de cette ancienne syntaxe.

Exemple 17. Requête jointe sur Marins et Communes - 2

select Marins.nom, Marins.prenom, Communes.nom
    from Marins, Communes 
    where Marins.id_commune = Communes.id 
    order  by Marins.nom ;

3.2. Clés primaires et clés étrangères

La façon dont nous avons traité le problème de l'association entre un marin et sa commune de naissance est tout à fait correcte, mais elle a besoin d'être sécurisée. Effectivement, deux problèmes peuvent se poser :
  • que se passe-t-il si la collone ID n'est pas renseignée pour une commune donnée ?
  • que se passe-t-il si plusieurs communes ont le même ID ?
  • que se passe-t-il si l'on crée un marin avec une valeur incorrecte de id_commune ?
  • que se passe-t-il si l'on efface une commune référencée par une cellule id_commune ?
À chacune de ces questions une réponse SQL apportera. Déclarer la colonne ID de la table Communes comme étant une clé primaire répond aux deux premières interrogations. Cette notion est centrale en SQL, et nous la reverrons en détails. Indiquons ici simplement qu'une colonne déclarée comme étant une clé primaire d'une table ne peut pas prendre de valeurs nulles, et ne peut pas porter de doublons. Notons toutefois, qu'il est possible en SQL d'empêcher la présence de doublons dans une colonne, de même que la présence de valeurs nulles, sans utiliser le mécanisme des clés primaires. Les deux dernières interrogations sont un peu plus subtiles : on souhaite établir une double contrainte entre une colonne d'une table, et une colonne d'une autre table. Effectivement, lors de l'insertion d'un nouveau marin, on souhaite que id_commune, s'il est non nul, référence obligatoirement une valeur de la colonne ID de la table Communes, et que réciproquement, il ne soit pas possible d'effacer une commune si elle est référencée par un marin. L'outil que nous fournit SQL pour ce faire et la clé étrangère . Une colonne peut être déclarée comme étant une clé étrangère. Dans ce cas, les valeurs qu'elle porte doivent correspondre à celles d'une autre colonne, en général d'une autre table. Si, lors de la création, la valeur insérée ne correspond à aucune valeur, alors l'insertion échoue. De l'autre côté de la relation, si l'on tente d'effacer la valeur référencée, une erreur est générée, qui peut être gérée applicativement ou non.
Introduction à SQL
Retour au blog Java le soir
Cours & Tutoriaux
Table des matières
Introduction
1. Un peu d'histoire
2. Organisation de la présentation
Un premier exemple
1. Un premier exemple
1.1. Construction d'une première table
1.2. Enregistrer quelques données
1.3. Extraire des données
2. Trier, classer, calculer
2.1. Classer des données
2.2. Trier des données
2.3. Effectuer des calculs
2.4. Mise à jour d'une valeur
3. Sélection sur plusieurs tables
3.1. Ajout du lieu de naissance
3.2. Clés primaires et clés étrangères
Organisation des données
1. Introduction
2. Bases de données, schémas et tables
2.1. Création d'une table
2.2. Création d'une colonne
2.3. Contraintes sur une table
2.4. Nommage des contraintes
2.5. Exemples de création de tables
2.6. Modification d'une table
2.7. Effacement d'une table
2.8. Remarques sur restrict et cascade
3. Types de données
3.1. Les types numériques
3.2. Les types caractère
3.3. Les types temporels
3.4. Les types binaires
3.5. Type auto-incrémental de MySQL
3.6. Type auto-généré de Derby
3.7. Séquences d'Oracle
Manipulation des données
1. Introduction
2. Extraire des données : select
2.1. Extraire des données d'une table unique
2.2. Clause where
2.3. Requêtes imbriquées
2.4. Fonctions d'agrégation, groupage
3. Supprimer des données : delete
3.1. Forme générale du delete
3.2. Effacement en cascade
3.3. Fonctionnement du delete
4. Ajouter des données : insert
4.1. Forme générale de l' insert
4.2. Copie d'une table dans une autre
5. Mettre à jour des données : update
5.1. Forme générale de l' update
5.2. Mise à jour avec une requête imbriquée
Interrogations sur plusieurs tables
1. Introduction
2. Formes normales
2.1. Première forme normale
2.2. Deuxième forme normale
2.3. Troisième forme normale
2.4. Formes normales d'ordres supérieurs
3. Relations entre éléments
3.1. Cardinalité d'une relation
3.2. Relation 1:1
3.3. Relation 1:p
3.4. Relation p:1
3.5. Relation n:p
4. Jointures
4.1. Jointure interne
4.2. Jointure externe
4.3. Auto-jointure
5. Unions
6. Vues
6.1. Création d'une vue
6.2. Exemples de vues
Transactions
1. Introduction
2. Isolation des transactions
2.1. ACIDité d'une transaction
2.2. Définition de l'isolation
3. Gestion d'une transaction
3.1. Mode auto-commit
3.2. Fixer le niveau d'isolation
3.3. Démarrer une transaction
3.4. Terminer une transaction
3.5. Remarques importantes
Index
1. Introduction
2. Manipulation d'index
2.1. Création automatique d'index
2.2. Création manuelle d'index