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) :

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 :

{"op": "UNION"} {"op": "UNION ALL"} {"op": "INTERSECT"} {"op": "EXCEPT"}

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 :

Par exemple, pour utiliser une sous-requête dans une condition :

{"cond": ">", "subquery": "SELECT AVG(ID) FROM T2"} {"cond": ">", "subquery": "SELECT ID FROM T2"} {"cond": "IN", "subquery": "SELECT ID FROM T2"}

💡 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 :

{"cond": "EXISTS"}

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 à :

⚠ 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. :

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 .

x = -- WHERE -->
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.

JOIN =

💡 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 :T1xT2T1.ID == T2.ID
LEFT :T1xNULLT1.ID NOT IN T2.ID(+ entrées INNER)
FULL :NULLxT2T2.ID NOT IN T1.ID(+ entrées LEFT)
{"jointype": "INNER"} {"jointype": "LEFT"} {"jointype": "RIGHT"} {"jointype": "FULL"}

⚠ Il existe 2 autres types de jointures, à é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 :

⚠ 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.

{"jointype": "INNER"} {"joincond": "USING(ID)"} {"joincond": "ON T1.T1 == T2.T2"}

⚠ 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 :

{"cols": "*"} {"cols": "*, T2.ID as ID2"}