Jointures et sous-requêtes : modification de données

Voici un petit chapitre bonus sur les jointures et les sous-requêtes. Vous allez apprendre ici à utiliser ces outils, non pas dans le cadre de la sélection de données comme on l'a fait jusqu'à présent, mais pour :

  • l'insertion : les sous-requêtes vont permettre d'insérer des données dans une table à partir de données venant d'autres tables (mais pas exclusivement) ;
  • la modification : jointures et sous-requêtes vont permettre non seulement d'utiliser des critères de sélection complexes mais également d'aller chercher les nouvelles valeurs dans d'autres tables ;
  • la suppression de données : comme pour la modification, les critères de sélection pourront être plus complexes grâce aux jointures et sous-requêtes.

Insertion

Pour l'insertion nous n'allons nous servir que de sous-requêtes, pas de jointures. Quoique… La sous-requête pourrait très bien contenir une jointure !

Sous-requête pour l'insertion

Vous venez d'acquérir un magnifique Maine Coon chez un éleveur voisin. Vous vous apprêtez à l'insérer dans votre base de données, mais vous ne vous souvenez absolument pas de l'id de la race Maine Coon, ni de celui de l'espèce Chat. Du coup, deux possibilités s'offrent à vous.

  • Vous pouvez faire une requête pour sélectionner l'id de la race et de l'espèce, puis faire ensuite une requête d'insertion avec les données que vous venez d'afficher.
  • Vous pouvez utiliser une sous-requête pour insérer directement l'id de la race et de l'espèce à partir du nom de la race.

Je ne sais pas ce que vous en pensez, mais moi je trouve la seconde option bien plus sexy !

Donc, allons-y. Qu'avons-nous comme information ?

  • Nom : Yoda
  • Date de naissance : 2010-11-09
  • Sexe : mâle
  • Espèce : chat
  • Race : Maine coon

De quoi avons-nous besoin en plus pour l'insérer dans notre table ? L'id de l'espèce et de la race. Comment récupérer ces deux id ? Ça, vous savez faire : une simple requête suffit.

1
SELECT id AS race_id, espece_id FROM Race WHERE nom = 'Maine coon';

race_id

espece_id

5

2

Bien, mais le but était de tout faire en une seule requête, pas d'insérer nous-mêmes 5 et 2 après les avoir récupérés.

C'est ici que les sous-requêtes interviennent. Nous allons utiliser une nouvelle syntaxe d'insertion.

INSERT INTO… SELECT

Cette syntaxe permet de sélectionner des éléments dans des tables, afin de les insérer directement dans une autre.

1
2
3
4
5
INSERT INTO nom_table
   [(colonne1, colonne2, ...)]
SELECT [colonne1, colonne2, ...]
FROM nom_table2
[WHERE ...]

Vous n'êtes bien sûr pas obligés de préciser dans quelles colonnes se fait l'insertion, si vous sélectionnez une valeur pour toutes les colonnes de la table. Ce sera cependant rarement le cas puisque nous avons des clés primaires auto-incrémentées.

Avec cette requête, il est absolument indispensable (sinon l'insertion ne se fera pas), d'avoir le même nombre de colonnes dans l'insertion et dans la sélection, et qu'elles soient dans le même ordre. Si vous n'avez pas le même nombre de colonnes, cela déclenchera une erreur. De plus, si l'ordre n'est pas bon, vous aurez probablement une insertion erronée.

Nous allons donc insérer le résultat de notre requête qui sélectionne les id de l'espèce et la race directement dans notre table Animal. Pas de souci, mais il faut également insérer le nom, le sexe, etc.

En effet ! Mais c'est très facile. Souvenez-vous, vous pouvez très bien faire des requêtes de ce type :

1
SELECT 'Yoda' AS nom;

Et donc, en combinant avec notre requête précédente :

1
SELECT id AS race_id, espece_id FROM Race WHERE nom = 'Maine coon';

Vous pouvez obtenir très facilement, et en une seule requête, tous les renseignements indispensables à l'insertion de notre petit Yoda !

1
2
SELECT 'Yoda', 'M', '2010-11-09', id AS race_id, espece_id
FROM Race WHERE nom = 'Maine coon';

Attention à ne pas oublier les guillemets autour des chaînes de caractères, sinon MySQL va essayer de trouver la colonne Yoda de la table Race, et forcément ça générera une erreur.

Si tout se passe bien, cette requête devrait vous donner ceci :

Yoda

M

2010-11-09

race_id

espece_id

Yoda

M

2010-11-09

5

2

Les noms qui sont donnés aux colonnes n'ont pas d'importance, mais vous pouvez les changer avec des alias si cela vous perturbe.

Venons-en maintenant à notre super insertion !

1
2
3
4
INSERT INTO Animal 
    (nom, sexe, date_naissance, race_id, espece_id)              -- Je précise les colonnes puisque je ne donne pas une valeur pour toutes.
SELECT  'Yoda', 'M', '2010-11-09', id AS race_id, espece_id     -- Attention à l'ordre !
FROM Race WHERE nom = 'Maine coon';

Sélectionnons maintenant les Maine coon de notre base, pour vérifier que l'insertion s'est faite correctement.

1
2
3
4
5
SELECT Animal.id, Animal.sexe, Animal.nom, Race.nom AS race, Espece.nom_courant as espece
FROM Animal
INNER JOIN Race ON Animal.race_id = Race.id
INNER JOIN Espece ON Race.espece_id = Espece.id
WHERE Race.nom = 'Maine coon';

Et qui voyons-nous apparaître dans les résultats ? Notre petit Yoda !

id

sexe

nom

race

espece

8

M

Bagherra

Maine coon

Chat

30

M

Zonko

Maine coon

Chat

32

M

Farceur

Maine coon

Chat

34

M

Capou

Maine coon

Chat

39

F

Zara

Maine coon

Chat

40

F

Milla

Maine coon

Chat

42

F

Bilba

Maine coon

Chat

43

F

Cracotte

Maine coon

Chat

61

F

Yoda

Maine coon

Chat

Modification

Utilisation des sous-requêtes

Pour la sélection

Imaginez que pour une raison bizarre, vous vouliez que tous les perroquets aient en commentaire "Coco veut un gâteau !".

Si vous saviez que l'id de l'espèce est 4, ce serait facile :

1
UPDATE Animal SET commentaires = 'Coco veut un gâteau' WHERE espece_id = 4;

Seulement voilà, vous ne savez évidemment pas que l'id de cette espèce est 4. Sinon, ce n'est pas drôle ! Vous allez donc utiliser une magnifique sous-requête pour modifier ces bruyants volatiles !

1
2
UPDATE Animal SET commentaires = 'Coco veut un gâteau !' WHERE espece_id = 
    (SELECT id FROM Espece WHERE nom_courant LIKE 'Perroquet%');

Bien sûr, toutes les possibilités de conditions que l'on a vues pour la sélection sont encore valables pour les modifications. Après tout, une clause WHERE est une clause WHERE !

Pour l'élément à modifier

Ce matin, un client demande à voir vos chats Bleu Russe, car il compte en offrir un à sa fille. Ni une ni deux, vous vérifiez dans la base de données, puis allez chercher Schtroumpfette, Filou, Caribou, Raccou, Callune, Feta et Cawette. Et là, horreur et damnation ! À l'instant où ses yeux se posent sur Cawette, le client devient vert de rage. Il prend à peine le temps de vous expliquer, outré, que Cawette n'est pas un Bleu Russe mais bien un Nebelung, à cause de ses poils longs, puis s'en va chercher un éleveur plus compétent.

Bon… L'erreur est humaine, mais autant la réparer rapidement. Vous insérez donc une nouvelle race dans la table ad hoc.

1
2
INSERT INTO Race (nom, espece_id, description)
VALUES ('Nebelung', 2, 'Chat bleu russe, mais avec des poils longs...');

Une fois cela fait, il vous reste encore à modifier la race de Cawette. Pour cela, vous avez besoin de l'id de la race Nebelung que vous venez d'ajouter. Vous vous en doutez, il est tout à fait possible de le faire grâce à une sous-requête :

1
2
3
UPDATE Animal SET race_id = 
    (SELECT id FROM Race WHERE nom = 'Nebelung' AND espece_id = 2)
WHERE nom = 'Cawette';

Il est bien entendu indispensable que le résultat de la sous-requête soit une valeur !

Limitation des sous-requêtes dans un UPDATE

Une limitation importante des sous-requêtes est qu'on ne peut pas modifier un élément d'une table que l'on utilise dans une sous-requête.

Exemple : vous trouvez que Callune ressemble quand même fichtrement à Cawette, et ses poils sont aussi longs. Du coup, vous vous dites que vous auriez dû également modifier la race de Callune. Vous essayez donc la requête suivante :

1
2
3
UPDATE Animal SET race_id = 
    (SELECT race_id FROM Animal WHERE nom = 'Cawette' AND espece_id = 2)
WHERE nom = 'Callune';

Malheureusement :

1
ERROR 1093 (HY000): You can't specify target table 'Animal' for update in FROM clause

La sous-requête utilise la table Animal, or vous cherchez à modifier le contenu de celle-ci. C'est impossible !

Il vous faudra donc utiliser la même requête que pour Cawette, en changeant simplement le nom (je ne vous fais pas l'affront de vous l'écrire).

Modification avec jointure

Imaginons que vous vouliez que, pour les tortues et les perroquets, si un animal n'a pas de commentaire, on lui ajoute comme commentaire la description de l'espèce. Vous pourriez sélectionner les descriptions, les copier, retenir l'id de l'espèce, et ensuite faire un UPDATE pour les tortues et un autre pour les perroquets. Ou alors, vous pourriez simplement faire un UPDATE avec jointure !

Voici la syntaxe que vous devriez utiliser pour le faire avec une jointure :

1
2
3
4
5
6
UPDATE Animal                                                             -- Classique !
INNER JOIN Espece                                                         -- Jointure.
    ON Animal.espece_id = Espece.id                                        -- Condition de la jointure.
SET Animal.commentaires = Espece.description                               -- Ensuite, la modification voulue.
WHERE Animal.commentaires IS NULL                                         -- Seulement s'il n'y a pas encore de commentaire.
AND Espece.nom_courant IN ('Perroquet amazone', 'Tortue d''Hermann');      -- Et seulement pour les perroquets et les tortues.
  • Vous pouvez bien sûr mettre ce que vous voulez comme modifications. Ici, j'ai utilisé la valeur de la colonne dans l'autre table, mais vous auriez pu mettre Animal.commentaires = 'Tralala', et la jointure n'aurait alors servi qu'à sélectionner les tortues et les perroquets grâce au nom courant de l'espèce.
  • Toutes les jointures sont possibles. Vous n'êtes pas limités aux jointures internes, ni à deux tables jointes.

Suppression

Cette partie sera relativement courte, puisque l'utilisation des sous-requêtes et des jointures est assez ressemblante entre la suppression et la modification. Simplement, pour la suppression, les sous-requêtes et jointures ne peuvent servir qu'à sélectionner les lignes à supprimer.

Utilisation des sous-requêtes

On peut, tout simplement, utiliser une sous-requête dans la clause WHERE. Par exemple, imaginez que nous ayons deux animaux dont le nom est "Carabistouille", un chat et un perroquet. Vous désirez supprimer Carabistouille-le-chat, mais garder Carabistouille-le-perroquet. Vous ne pouvez donc pas utiliser la requête suivante, qui supprimera les deux :

1
DELETE FROM Animal WHERE nom = 'Carabistouille';

Mais il suffit d'une sous-requête dans la clause WHERE pour sélectionner l'espèce, et le tour est joué !

1
2
3
DELETE FROM Animal
WHERE nom = 'Carabistouille' AND espece_id = 
    (SELECT id FROM Espece WHERE nom_courant = 'Chat');

Limitations

Les limitations sur DELETE sont les mêmes que pour UPDATE : on ne peut pas supprimer des lignes d'une table qui est utilisée dans une sous-requête.

Suppression avec jointure

Pour les jointures, c'est le même principe. Si je reprends le même problème que ci-dessus, voici comment supprimer la ligne voulue avec une jointure :

1
2
3
4
DELETE Animal                                        -- Je précise de quelles tables les données doivent être supprimées
FROM Animal                                          -- Table principale
INNER JOIN Espece ON Animal.espece_id = Espece.id    -- Jointure     
WHERE Animal.nom = 'Carabistouille' AND Espece.nom_courant = 'Chat';

Vous remarquez une petite différence avec la syntaxe "classique" de DELETE (sans jointure) : je précise le nom de la table dans laquelle les lignes doivent être supprimées juste après le DELETE. En effet, comme on utilise plusieurs tables, cette précision est obligatoire. Ici, on ne supprimera que les lignes d'Animal correspondantes.


En résumé

  • INSERT INTO ... SELECT ... permet d'insérer dans une table des données provenant directement d'une autre (ou un mélange de données provenant d'une table, et de constantes définies par l'utilisateur).
  • On peut utiliser des jointures et des sous-requêtes pour créer des critères de sélection complexes pour une modification ou une suppression de données.
  • Dans le cas d'une suppression avec jointure, il est indispensable de préciser de quelle(s) table(s) les données doivent être supprimées
  • On peut utiliser une sous-requête dans la clause SET d'une modification de données, pour définir la nouvelle valeur d'un champ à partir d'une autre table.