Fusions et jointures
Opérations d'ensembles
Il est possible d'opérer des opérations sur deux ensembles d'entrées, à conditions qu'elles aient le même nombre de colonnes (et idéalement la même structure) :
- : concaténer les lignes [avec les doublons].
- : les lignes communes aux deux requêtes.
- : les lignes de la première requête absentes de la seconde.
Les opérations d'ensembles sont en pratique relativement rares. Elles peuvent toutefois être pertinentes quand une table a été divisée en plusieurs tables pour des raisons de performances. Par exemple, une table contenant de très nombreuses entrées, et dont la manipulation se fait quasi-exclusivement en précisant une année de vente, pourrait être découpée en vue d'obtenir une table par année.
Ces opérations s'utilisent sous la forme suivante :
Sous requêtes
Vous pouvez utiliser le résultat d'une requête (alors appelée "sous-requête") dans une autre requête. En effet, un ensemble d'entrées placées entre parenthèses peuvent remplacer :
- une table dans une clause ;
- une liste (si une seule colonne) ;
- un scalaire (si une seule ligne et colonne).
Par exemple, pour utiliser une sous-requête dans une condition :
💡 Les opérateurs de comparaisons utilisés sur une liste retourneront vrai si la condition est vraie pour au moins un élément de la liste.
⚠ Certains SGBD requièrent d'ajouter entre l'opérateur de comparaison et la liste.
Sous-requête corrélée
Lorsque la sous-requête dépend d'une colonne de la requête principale, elle est dite "corrélée", et sera réexécutée à chaque ligne de la requête principale. Dès lors, pour des raisons de performances on utilisera les opérateurs :
- à la place de .
- / ou au lieu d'utiliser certaines fonctions d'agrégations.
En effet, une sous-requête non-corrélée n'étant exécutée qu'une seule fois, il est intéressant de l'évaluer entièrement en amont (i.e. récupérer toutes les lignes), pour ensuite utiliser son résultat, à chaque entrée de la requête principale, lors de l'évaluation la clause , sans avoir à le recalculer à chaque fois.
En revanche, quand une sous-requête est corrélée, il faudra nécessairement évaluer la sous-requête à chaque entrée de la requête principale. Cependant il n'est alors pas utile de calculer l'ensemble des lignes de la sous-requête, car on peut s'arrêter à :
- la première ligne existante pour .
- la première ligne satisfaisant la condition pour .
- la première ligne ne satisfaisant pas la condition pour .
⚠ Il va de soit qu'il faut éviter les sous-requêtes corrélées autant que possible...
⚠ SQLite ne supporte que , il ne supporte ni , ni .
Jointures
Pour rappel, une table (e.g. ) peut en référencer une autre (e.g. ). Il est alors possible d'obtenir e.g. :
- la liste des vendeurs ayant au moins 10 ventes ?
- la liste des ventes des vendeurs embauchés il y a moins de 5 ans.
Pour cela il est possible d'utiliser des requêtes corrélées, mais la syntaxe serait lourde et la requête peu performante. Dans ce cas de figure on utilise ce qu'on appelle des jointures, qui permettent de fusionner des entrées de tables différentes au sein d'une même ligne.
Le produit cartésien
Une méthode naïve (et à ne jamais utiliser) d'effectuer une jointure est d'utiliser un produit cartésien :
Pour exécuter cette requête, le SGDB va construire une table intermédiaire constituée de toutes les combinaisons de lignes possibles entre et , qu'elle va ensuite parcourir pour filtrer les lignes via la clause .
T1.ID == T2.ID
Ainsi, le produit cartésien de deux tables de 3 entrées produira une table intermédiaire de 9 lignes, dont la majorité des lignes seront ensuite rejetées par la clause . Même sur de petites tables, la construction de la table intermédiaire explose très vite les capacités du SGDB :
| # Entrées | # Tables | # Table intermédiaire | # Résultats |
|---|---|---|---|
| E | T | E^T | E |
| 1 000 | 2 | 1 000 000 | 1 000 |
| 1 000 | 3 | 1 000 000 000 | 1 000 |
| 32 000 | 2 | 1 024 000 000 | 32 000 |
| 1 000 | 6 | 10^18 | 1 000 |
| 1 000 000 | 3 | 10^18 | 1 000 000 |
Pour rappel, 1 milliard de lignes correspondent à plusieurs Go en mémoire, et 10^18 à plusieurs Po !!!
⚠ Pour cette raison, on n'utilise JAMAIS les produits cartésien en SQL ! JA-MAIS !
Le principe des jointures
La bonne manière de procéder est d'utiliser la clause qui s'utilise usuellement de la sorte :
Pour chaque entrée de , le SGBD va rechercher les entrées de dont les valeurs des colonnes communes sont identiques à celles de l'entrée de . Ainsi, contrairement au produit cartésien, le SGBD n'a pas besoin de construire une table intermédiaire colossale, réduisant très fortement la consommation de mémoire vive.
💡 Si les colonnes en communs constituent un index ( ou clef primaire), la recherche des entrées de est quasi instantanée.
⚠ Il est ainsi plus que fortement recommandé d'effectuer des jointures sur des clés primaires.
💡 Vous pouvez ajouter une clause à vôtre requête SQL. En théorie la clause est appliquée aux entrées après jointures. Cependant, les SGBD sont capables d'optimiser la requête en préfiltrant, lorsque possible, les tables avant jointures.
Les types de jointures
En réalité, il existe 3 types de jointures fréquemment utilisées :
| Entries | WHERE | ||||
|---|---|---|---|---|---|
| INNER : | T1 | x | T2 | T1.ID == T2.ID | |
| LEFT : | T1 | x | NULL | T1.ID NOT IN T2.ID | (+ entrées INNER) |
| FULL : | NULL | x | T2 | T2.ID NOT IN T1.ID | (+ entrées LEFT) |
- : par défaut.
- : comme , mais ajoute au résultat les entrées de dont aucune entrée de ne correspond.
- (plus rare) : comme , mais ajoute au résultat les entrées de dont aucune entrée de ne correspond.
💡 Il est équivalant à l'union de et .
⚠ Il existe 2 autres types de jointures, à éviter :
- : est équivalant à
⚠ Pour des raisons de lisibilité, autant que possible, utilisez à la place. - : produit cartésien (**à éviter**).
💡 Vous pourrez aussi trouver dans certaines requêtes SQL. Ils sont en réalité équivalents à .
Préciser la condition de jointure
Il se peut que vous souhaitiez expliciter les colonnes sur lesquelles effectuer la jointure pour :
- exclure une colonne de la jointure, avec :
indiquant les colonnes à utiliser. - utiliser des colonnes de noms différents, avec :
indiquant la condition de jointure.
⚠ Dans les deux cas, il vous faudra retirer le mot clef .
💡 Il est recommandé d'utiliser au lieu de jointures naturelles (i.e. avec ) afin d'éviter des jointures accidentelles.
⚠ Si deux colonnes ont le même nom, seule la première sera affichée. Si vous souhaitez afficher la seconde, il est alors nécessaire de la renommer :