Union de plusieurs requêtes

Toujours dans l'optique de rassembler plusieurs requêtes en une seule, voici l'UNION. Faire l'union de deux requêtes, cela veut simplement dire réunir les résultats de la première requête et les résultats de la seconde requête. Voyons comment ça fonctionne !

Syntaxe

La syntaxe d'UNION est simplissime : vous avez deux requêtes SELECT dont vous voulez additionner les résultats ; il vous suffit d'ajouter UNION entre ces deux requêtes.

1
2
3
SELECT ...
UNION
SELECT ...

Le nombre de requêtes qu'il est possible d'unir est illimité. Si vous avez cinquante requêtes de sélection, placez un UNION entre les cinquante requêtes.

1
2
3
4
5
6
7
8
SELECT ...
UNION
SELECT ...
UNION
SELECT ...
....
UNION
SELECT ...

Par exemple, vous pouvez obtenir les chiens et les tortues de la manière suivante :

1
2
3
4
5
6
7
SELECT Animal.* FROM Animal 
INNER JOIN Espece ON Animal.espece_id = Espece.id 
WHERE Espece.nom_courant = 'Chat'
UNION
SELECT Animal.* FROM Animal 
INNER JOIN Espece ON Animal.espece_id = Espece.id 
WHERE Espece.nom_courant = 'Tortue d''Hermann';

Cette requête peut bien sûr être écrite sans UNION, en faisant tout simplement un seul SELECT avec WHERE Espece.nom_courant = 'Chat' OR Espece.nom_courant = 'Tortue d''Hermann'. Il ne s'agit ici que d'un exemple destiné à illustrer la syntaxe. Nous verrons plus loin des exemples de cas où UNION est indispensable.

Les règles

Bien sûr, ce n'est pas si simple que ça, il faut respecter certaines contraintes.

Nombre de colonnes

Il est absolument indispensable que toutes les requêtes unies renvoient le même nombre de colonnes.

Dans la requête que l'on a écrite ci-dessus, aucun problème puisque l'on sélectionne Animal.* dans les deux requêtes. Mais il ne serait donc pas possible de sélectionner un nombre de colonnes différent dans chaque requête intermédiaire.

Par exemple, la requête ci-dessous renverra une erreur :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
-- Pas le même nombre de colonnes --
------------------------------------

SELECT Animal.id, Animal.nom, Espece.nom_courant                    -- 3 colonnes sélectionnées
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
WHERE Espece.nom_courant = 'Chat'
UNION
SELECT Animal.id, Animal.nom, Espece.nom_courant, Animal.espece_id -- 4 colonnes sélectionnées
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
WHERE Espece.nom_courant = 'Tortue d''Hermann';
1
ERROR 1222 (21000): The used SELECT statements have a different number of columns

Type et ordre des colonnes

En ce qui concerne le type des colonnes, je pense vous avoir déjà signalé que MySQL est très (vraiment très) permissif. Par conséquent, si vous sélectionnez des colonnes de différents types, vous n'aurez pas d'erreurs, mais vous aurez des résultats un peu… spéciaux. o_O

Prenons la requête suivante :

1
2
3
4
5
6
7
8
9
SELECT Animal.id, Animal.nom, Espece.nom_courant    -- 3e colonne : nom_courant VARCHAR
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
WHERE Espece.nom_courant = 'Chat' AND Animal.nom LIKE 'C%'
UNION
SELECT Animal.id, Animal.nom, Espece.id             -- 3e colonne : id SMALLINT
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
WHERE Espece.nom_courant = 'Tortue d''Hermann' AND Animal.nom LIKE 'C%';

Vous aurez bien un résultat :

id

nom

nom_courant

5

Choupi

Chat

33

Caribou

Chat

34

Capou

Chat

37

Callune

Chat

43

Cracotte

Chat

44

Cawette

Chat

50

Cheli

3

51

Chicaca

3

Mais avouez que ce n'est pas un résultat très cohérent. MySQL va simplement convertir tout en chaînes de caractères pour ne pas avoir de problème. Soyez donc très prudents !

Vous pouvez constater en passant que les noms de colonnes utilisés dans les résultats sont ceux de la première requête effectuée. Vous pouvez bien sûr les renommer avec des alias.

Et enfin, pour l'ordre des colonnes, à nouveau vous n'aurez pas d'erreur tant que vous avez le même nombre de colonnes dans chaque requête, mais vous pouvez avoir des résultats bizarres. En effet, MySQL n'analyse pas les noms des colonnes pour trouver une quelconque correspondance d'une requête à l'autre ; tout se fait sur la base de la position de la colonne dans la requête.

1
2
3
4
5
6
7
SELECT Animal.id, Animal.nom, Espece.nom_courant FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
WHERE Espece.nom_courant = 'Chat' AND Animal.nom LIKE 'C%'
UNION
SELECT Animal.nom, Animal.id, Espece.nom_courant FROM Animal   -- 1e et 2e colonnes inversées par rapport à la première requête
INNER JOIN Espece ON Animal.espece_id = Espece.id
WHERE Espece.nom_courant = 'Tortue d''Hermann' AND Animal.nom LIKE 'C%';

id

nom

nom_courant

5

Choupi

Chat

33

Caribou

Chat

34

Capou

Chat

37

Callune

Chat

43

Cracotte

Chat

44

Cawette

Chat

Cheli

50

Tortue d'Hermann

Chicaca

51

Tortue d'Hermann

UNION ALL

Exécutez la requête suivante :

1
2
3
SELECT * FROM Espece
UNION
SELECT * FROM Espece;

Non, non, je ne me suis pas trompée, je vous demande bien d'unir deux requêtes qui sont exactement les mêmes.

Résultat :

id

nom_courant

nom_latin

description

1

Chien

Canis canis

Bestiole à quatre pattes qui aime les caresses et tire souvent la langue

2

Chat

Felis silvestris

Bestiole à quatre pattes qui saute très haut et grimpe aux arbres

3

Tortue d'Hermann

Testudo hermanni

Bestiole avec une carapace très dure

4

Perroquet amazone

Alipiopsitta xanthops

Joli oiseau parleur vert et jaune

Chaque résultat n’apparaît qu'une seule fois. Pour la simple et bonne raison que lorsque vous faites UNION, les doublons sont effacés. En fait, UNION est équivalent à UNION DISTINCT. Si vous voulez conserver les doublons, vous devez utiliser UNION ALL.

1
2
3
SELECT * FROM Espece
UNION ALL
SELECT * FROM Espece;

id

nom_courant

nom_latin

description

1

Chien

Canis canis

Bestiole à quatre pattes qui aime les caresses et tire souvent la langue

2

Chat

Felis silvestris

Bestiole à quatre pattes qui saute très haut et grimpe aux arbres

3

Tortue d'Hermann

Testudo hermanni

Bestiole avec une carapace très dure

4

Perroquet amazone

Alipiopsitta xanthops

Joli oiseau parleur vert et jaune

1

Chien

Canis canis

Bestiole à quatre pattes qui aime les caresses et tire souvent la langue

2

Chat

Felis silvestris

Bestiole à quatre pattes qui saute très haut et grimpe aux arbres

3

Tortue d'Hermann

Testudo hermanni

Bestiole avec une carapace très dure

4

Perroquet amazone

Alipiopsitta xanthops

Joli oiseau parleur vert et jaune

Il n'est pas possible de mélanger UNION DISTINCT et UNION ALL dans une même requête. Si vous utilisez les deux, les UNION ALL seront considérés comme des UNION DISTINCT.

LIMIT et ORDER BY

LIMIT

Il est possible de restreindre les résultats avec LIMIT au niveau de la requête entière, ou au niveau des différentes requêtes unies. L'important est que ce soit clair. Par exemple, vous unissez deux requêtes et vous voulez limiter les résultats de la première. La requête suivante est parfaite pour ça :

1
2
3
SELECT id, nom, 'Race' AS table_origine FROM Race LIMIT 3
UNION
SELECT id, nom_latin, 'Espèce' AS table_origine FROM Espece;

Vous avez bien trois noms de races, suivis de toutes les espèces.

id

nom

table_origine

1

Berger allemand

Race

2

Berger blanc suisse

Race

3

Boxer

Race

4

Alipiopsitta xanthops

Espèce

1

Canis canis

Espèce

2

Felis silvestris

Espèce

3

Testudo hermanni

Espèce

En passant, voici un résultat qu'il n'est pas possible d'obtenir sans utiliser UNION !

Par contre, si vous voulez limiter les résultats de la seconde requête, comment faire ? Essayons la requête suivante.

1
2
3
SELECT id, nom, 'Race' AS table_origine FROM Race
UNION
SELECT id, nom_latin, 'Espèce' AS table_origine FROM Espece LIMIT 2;

id

nom

table_origine

1

Berger allemand

Race

2

Berger blanc suisse

Race

Visiblement, ce n'est pas ce que nous voulions… En fait, LIMIT a été appliqué à l'ensemble des résultats, après UNION. Par conséquent, si l'on veut que LIMIT ne porte que sur la dernière requête, il faut le préciser. Pour ça, il suffit d'utiliser des parenthèses.

1
2
3
SELECT id, nom, 'Race' AS table_origine FROM Race
UNION
(SELECT id, nom_latin, 'Espèce' AS table_origine FROM Espece LIMIT 2);

id

nom

table_origine

1

Berger allemand

Race

2

Berger blanc suisse

Race

3

Boxer

Race

4

Bleu russe

Race

5

Maine coon

Race

6

Singapura

Race

7

Sphynx

Race

8

Nebelung

Race

4

Alipiopsitta xanthops

Espèce

1

Canis canis

Espèce

Et voilà le travail !

ORDER BY

Par contre, s'il est possible de trier le résultat final d'une requête avec UNION, on ne peut pas trier les résultats des requêtes intermédiaires. Par exemple, cette requête trie bien les résultats par ordre anti-alphabétique du nom :

1
2
3
4
SELECT id, nom, 'Race' AS table_origine FROM Race
UNION
SELECT id, nom_latin, 'Espèce' AS table_origine FROM Espece
ORDER BY nom DESC;

Il faut bien mettre ici ORDER BY nom, et surtout pas ORDER BY Race.nom ou ORDER BY Espece.nom_latin. En effet, l'ORDER BY agit sur l'ensemble de la requête, donc en quelque sorte, sur une table intermédiaire composée des résultats des deux requêtes unies. Cette table n'est pas nommée, et ne possède que deux colonnes : id et nom (définies par la première clause SELECT rencontrée).

id

nom

table_origine

3

Testudo hermanni

Espèce

7

Sphynx

Race

6

Singapura

Race

8

Nebelung

Race

5

Maine coon

Race

2

Felis silvestris

Espèce

1

Canis canis

Espèce

3

Boxer

Race

4

Bleu russe

Race

2

Berger blanc suisse

Race

1

Berger allemand

Race

4

Alipiopsitta xanthops

Espèce

Vous pouvez bien sûr combiner LIMIT et ORDER BY.

1
2
3
4
(SELECT id, nom, 'Race' AS table_origine FROM Race LIMIT 6)
UNION
(SELECT id, nom_latin, 'Espèce' AS table_origine FROM Espece LIMIT 3)
ORDER BY nom LIMIT 5;

id

nom

table_origine

4

Alipiopsitta xanthops

Espèce

1

Berger allemand

Race

2

Berger blanc suisse

Race

4

Bleu russe

Race

3

Boxer

Race

Exception pour les tris sur les requêtes intermédiaires

Je vous ai dit qu'il n'était pas possible de trier les résultats d'une requête intermédiaire. En réalité, c'est plus subtil que ça. Dans une requête intermédiaire, il est possible d'utiliser un ORDER BY mais uniquement combiné à un LIMIT. Cela permettra de restreindre les résultats voulus (les X premiers dans l'ordre défini par l'ORDER BY par exemple).

Prenons la requête suivante :

1
2
3
(SELECT id, nom, 'Race' AS table_origine FROM Race LIMIT 6)
UNION
(SELECT id, nom_latin, 'Espèce' AS table_origine FROM Espece LIMIT 3);

Cela vous renvoie bien 6 races et 3 espèces, mais imaginons que vous ne vouliez pas n'importe quelles races, mais les 6 dernières par ordre alphabétique du nom. Dans ce cas-là, vous pouvez utiliser ORDER BY en combinaison avec LIMIT dans la requête intermédiaire :

1
2
3
(SELECT id, nom, 'Race' AS table_origine FROM Race ORDER BY nom DESC LIMIT 6)
UNION
(SELECT id, nom_latin, 'Espèce' AS table_origine FROM Espece LIMIT 3);

id

nom

table_origine

7

Sphynx

Race

6

Singapura

Race

8

Nebelung

Race

5

Maine coon

Race

3

Boxer

Race

4

Bleu russe

Race

4

Alipiopsitta xanthops

Espèce

1

Canis canis

Espèce

2

Felis silvestris

Espèce


En résumé

  • Pour grouper les résultats de deux requêtes SELECT, il suffit de placer UNION entre les deux requêtes.
  • UNION est équivalent à UNION DISTINCT. Si l'on ne veut pas éliminer les doublons, il faut utiliser UNION ALL.
  • Il est possible de limiter les résultats d'un UNION (avec LIMIT), mais également de limiter les résultats de chaque requête composant l'UNION.
  • Par contre, ORDER BY n'aura d'influence que s'il est utilisé sur le résultat final d'un UNION.