- Dans les bases de données relationnelles, un inner join dépasse la simple syntaxe SQL : une même structure peut s’interpréter différemment comme recherche, boucle imbriquée, modèle logique, vérification de types ou point de vue algébrique
- Dans des tables normalisées, la jointure devient l’outil le plus pratique pour suivre les références et recombiner des informations stockées sans duplication
- Du point de vue de l’implémentation, on peut la voir comme un parcours des paires de lignes qui ne conserve que les combinaisons satisfaisant une condition, ou comme la sélection, dans les domaines de colonnes, des seules combinaisons de valeurs présentes dans les deux relations
- Dans les modèles de programmation, on peut expliquer la jointure avec
flatMap, le SQLLATERAL, la résolution du problème N+1 des ORM, la vérification de types fondée sur les traits en Rust, ou leandThende la monade Set - Mathématiquement, les chemins dans un graphe, le modèle minimal, la plus grande relation admissible, la borne supérieure minimale d’un ordre partiel et le produit d’anneau d’expressions relationnelles révèlent tous les mêmes propriétés de la jointure
Dans des données normalisées, une jointure devient une recherche
- Très concrètement, on peut voir une jointure comme le fait de rechercher une valeur, ou d’ajouter à des données existantes des informations redondantes
- L’exemple part d’une table qui stocke
user,countryetcountry_codeensemble- Pour une même valeur de
country,country_codeest répété, ce qui crée de la duplication - Si les données changent souvent, il faut mettre à jour toutes les occurrences en même temps, ce qui accroît les erreurs et l’inefficacité
- Pour une même valeur de
- Sous forme normalisée, la relation entre
countryetcountry_codeest séparée dans une table dédiée, et la table des utilisateurs ne référence quecountry_id - En faisant un
INNER JOINentreusersetcountriessurcountry_id, on peut retrouver la forme initialeuser,country,country_code - La suite suppose que les jointures se font implicitement sur les colonnes de même nom, sans s’attacher strictement aux détails de la syntaxe SQL
Point de vue de l’implémentation : joindre en parcourant lignes et colonnes
- Étant donnés deux ensembles
R,Set un prédicatp, une jointure parcourt tous lesr ∈ Rets ∈ S, puis n’émet que les cas oùp(r, s)est vrai- Si le produit cartésien de deux collections correspond à toutes les connexions de lignes possibles, la jointure en est le sous-ensemble qui satisfait la condition
- Du point de vue des colonnes, on considère le domaine de chaque colonne comme l’ensemble de ses valeurs possibles, puis on parcourt les combinaisons de valeurs de colonnes
- Si l’on a
R(a, b)etS(b, c), on parcourt les domaines dea,betc - On n’émet
[a, b, c]que lorsque(a, b)est dansRet(b, c)est dansS
- Si l’on a
La jointure comme réalités alternatives compatibles
- L’exemple de John et Sally explique la jointure comme une opération qui ne conserve que les réalités compatibles, dans une situation où chacun ne dispose que d’une partie des informations
- John connaît les combinaisons possibles entre son animal de compagnie et un animal errant, et Sally connaît aussi les combinaisons possibles entre son animal de compagnie et un animal errant
- Le cas où John a un chien et l’animal errant est un chien ne peut pas être vrai en même temps que le cas où Sally a un chat et l’animal errant est une souris
- C’est parce que les deux personnes doivent observer le même animal errant
- En joignant les deux tables sur
stray, on ne conserve, parmi les combinaisons animal de John·animal errant·animal de Sally, que celles qui ne se contredisent pas
Les jointures dans les modèles de programmation
flatMapest une fonction qui crée un nouveau tableau pour chaque élément du tableau d’origine, puis concatène les résultats ; elle peut servir à implémenter une jointureSELECT * FROM r INNER JOIN s ON ps’exprime parr.flatMap(x => s.filter(y => p(x, y)))- La syntaxe
LATERALde certaines variantes de SQL transforme la jointure en une forme deflatMap
- Si la partie droite de
LATERALne référence pas de colonne de gauche, elle est équivalente à un produit cartésien- La décorrélation de requêtes repose sur la suppression des références aux colonnes de droite au moyen de réécritures successives
- Le problème N+1, fréquent avec les ORM, peut aussi s’expliquer par les jointures
- Si l’on exécute une requête supplémentaire pour chaque ligne du résultat, les bases de données qui utilisent des connexions, comme Postgres, imposent un coût fixe élevé pour chaque requête individuelle
- Demander à la base de données « d’effectuer toutes ces recherches » produit une jointure comme
users INNER JOIN countries - Avec une base de données in-process comme Sqlite, ce problème est moins marqué
Chemins de graphe et modèle logique
- Comme une relation « met en relation » deux ensembles, on peut la représenter comme un graphe
- La table
usersrelie l’ensemble des noms d’utilisateurs à l’ensemble descountry_id - La relation qui relie
country_idaux codes pays à deux lettres peut aussi être représentée comme un graphe séparé
- La table
- Si l’ensemble de sommets de droite du premier graphe est le même que l’ensemble de sommets de gauche du second, on peut les fusionner
- Énumérer tous les chemins qui partent de l’ensemble de gauche, passent par un sommet central et arrivent à l’ensemble de droite donne la jointure des deux relations
- En logique formelle, on voit les relations comme des prédicats, et l’ensemble des faits qui rend vrai un ensemble d’énoncés comme un modèle
- Si
users(A, B)etcountries(B, C, D)sont vrais, on pose l’implication selon laquelleQ(A, B, C, D)est vrai - Plusieurs modèles peuvent satisfaire cette condition
- Pour obtenir le résultat standard, on choisit, parmi les modèles qui satisfont la condition, le plus petit modèle
- Ce plus petit modèle est identique au résultat de la jointure entre
usersetcountry
- Si
La jointure vue comme vérification de types
- Les systèmes de types de style ML ressemblent fortement à Prolog et Datalog, si bien qu’on peut les exprimer d’une façon proche des jointures
- Dans l’exemple en Rust, les relations sont définies comme des traits
UsersetCountryCodejouent le rôle de relationsSmudge,Sissel,Petee,Canada,UnitedStates,CA,USsont définis comme des types concrets
- Les implémentations de traits comme
(Smudge, Canada): Userset(Canada, CA): CountryCodecorrespondent aux lignes de la relation - Pour que
(A, B, C)appartienne à la jointure, il faut que(A, B): Userset(B, C): CountryCode test::<(Smudge, _, CA)>()réussit la vérification de types, tandis quetest::<(Smudge, _, US)>()échoue parce que(Canada, US): CountryCoden’est pas implémenté
La jointure comme opération de la monade Set
- L’exemple JavaScript avec
SomeetNonecommence par une façon de fusionner des records optionnels- Si deux records ont le même
country, ils sont fusionnés etSomeest renvoyé - S’ils ne sont pas compatibles, ou s’il n’y a pas de valeur,
Noneest renvoyé
- Si deux records ont le même
andThenextrait le contenu d’une valeur optionnelle et applique la fonction de fusion- En conservant la même fonction
combinemais en remplaçant le conteneur parRel, on peut traiter des ensembles de relationsRel.mapapplique une fonction à toutes les lignesRel.andThenconcatène avecflatMaples relations produites par chaque ligne
- En exécutant le même
combinesur la relationuserset la relationcountries, on obtient comme résultat de jointureSmudge,SisseletPeteeavec leurs codes pays ajoutés
Plus grande relation admissible et join d’un ordre partiel
- Une troisième relation
Tqui possède toutes les colonnes de deux relationsRetSest dite admissible si elle n’invente pas de nouvelles informations- Lorsqu’on restreint une ligne de
Taux colonnes deR, cette ligne doit exister dansR - De la même manière, lorsqu’on la restreint aux colonnes de
S, cette ligne doit aussi exister dansS
- Lorsqu’on restreint une ligne de
- Par exemple,
Smudge, Canada, USn’est pas admissible- Si l’on ne garde que
countryetcountry_code, on obtientCanada, US, qui n’est pas une ligne deS
- Si l’on ne garde que
- La relation vide est elle aussi admissible, mais la plus grande relation admissible contient
Smudge-Canada-CA,Sissel-Canada-CAetPetee-United States-US - Cette plus grande relation admissible est la jointure des deux relations
- Du point de vue d’un ordre partiel, on définit
R ≤ Qcomme suitQcontient toutes les colonnes deR- Lorsque chaque ligne de
Qest restreinte aux colonnes deR, elle devient une ligne deR
- Dans cet ordre partiel, la borne supérieure minimale
R ∨ Sdes deux relationsR,Sexiste, et c’est le join au même sens que la jointure relationnelle
La jointure comme produit d’anneau
- On peut aussi représenter les relations algébriquement
- Une ligne est représentée comme un produit de paires colonne-valeur
- Une relation est représentée comme une somme de plusieurs lignes
- Par exemple, le terme obtenu en multipliant
user = Smudgeetcountry_id = 1devient une ligne - Des règles sont ajoutées pour simplifier les expressions
- Idempotence :
[x = y][x = y] = [x = y] - Contradiction :
[x = y][x = z] = 0ify ≠ z
- Idempotence :
- En multipliant la relation utilisateurs
Ret la relation de lookup des paysS, puis en développant avec les lois de distributivité et de commutativité, les termes contradictoires disparaissent et seuls les termes compatibles restent - L’expression restante est
Smudge-1-Canada-CA,Sissel-1-Canada-CA,Petee-2-United States-US, ce qui correspond exactement à la jointure des deux relations - Cette approche peut aussi être vue comme une tensor contraction
1 commentaires
Avis sur Hacker News
J’ai commencé à trouver les jointures beaucoup plus faciles à comprendre en les pensant en dimensions spatiales
Si on met chaque dimension dans une table séparée, comme
Dim_X,Dim_Y,Dim_Z, et qu’on les relie avec le mêmeEntityId, on peut voir ça comme la construction de la position en 3D d’une entitéPour créer 3 dimensions, il faut au moins 2 jointures internes, et on peut étendre le même principe à des dimensions non spatiales comme le temps
Si on ne limite pas un instant précis, on obtient alors un rapport contenant toutes les positions qu’une entité a occupées au fil du temps
Les autres types de jointures deviennent aussi plus faciles à comprendre avec cette variante une fois qu’on a bien intégré le concept, au point de pouvoir “faire pivoter” le schéma mentalement
https://dbdb.io/db/hyperdex
EntityPosition(EntityId, X, Y, Z)Cela dit, l’idée d’assembler des fragments de plusieurs dimensions et de gérer des agrégations fait penser au data warehouse
JOINouINNER JOIN. Lister les tables dansFROMet écrire les conditions de jointure dans la clauseWHEREcomme des équations me paraît bien plus clairQuand plusieurs
JOINsont mélangés dans une clauseFROMcomplexe, c’est difficile à lire, et lire des conditions d’égalité dansWHEREsemble plus intuitifLe point de vue numéro 0, c’est que la jointure est un opérateur de l’algèbre relationnelle
https://en.m.wikipedia.org/wiki/Relational_algebra
La jointure naturelle
R ⋈ Sest l’ensemble des combinaisons de tuples dont les noms d’attributs communs sont identiques, et c’est une opération relationnelle correspondant auANDlogique⋈peut être vu comme un produit cartésien filtré par un prédicat pour éliminer les lignes qui ne doivent pas apparaître dans le résultat, et une grande partie de SQL se comprend bien sous cet angleDepuis quelques jours, je cherche des ressources sur l’implémentation de l’exécution/de la planification de requêtes, mais je trouve peu de contenu côté implémentation sur les prédicats, les index existants ou les jointures
Les résultats Google sont pollués par du contenu expliquant simplement comment utiliser ces fonctionnalités
Jusqu’ici, je n’ai trouvé que les ressources du CMU Database Group, et elles sont excellentes
https://pi3.informatik.uni-mannheim.de/~moer/querycompiler.p...
Le cours de TUM “Database Systems on Modern CPU Architectures” peut aussi être utile, et les supports de 2020 incluent l’ensemble des vidéos du cours
https://db.in.tum.de/teaching/ss20/moderndbs/?lang=en
https://www.sqlite.org/optoverview.html
https://www.sqlite.org/queryplanner.html
https://www.postgresql.org/docs/current/planner-optimizer.ht...
L’exécution des requêtes et leur planification sont en pratique presque deux sujets distincts
Côté articles sur l’optimisation des jointures, je pense que l’article fondateur de Selinger reste le meilleur
https://courses.cs.duke.edu/compsci516/cps216/spring03/paper...
Il ne prend pas en charge les jointures externes et des techniques plus efficaces ont été proposées depuis, mais pour quelqu’un qui regarde des optimiseurs de la famille System R, il reste très familier
Le fichier
src/backend/optimizer/READMEde Postgres contient aussi beaucoup d’éléments qu’on voit rarement ailleursLes cours d’Andy Pavlo à la CMU sont pratiquement les seules ressources en ligne qui expliquent ce sujet, et le PDF “Building Query Compilers”, bien qu’incomplet, rassemble les articles clés de Moerkotte et d’autres, ce qui en fait une lecture utile pour une implémentation moderne
Trouver les index applicables n’est généralement pas difficile : il suffit de voir s’il existe un sargable predicate, mais l’estimation de sélectivité est difficile, et l’estimation de sélectivité à travers des jointures est presque le problème le plus difficile pour un optimiseur
Par exemple, si on a
A=x AND B=y AND C=zet uniquement des informations de sélectivité/cardinalité pour les index(A,B)et(B,C), estimer la sélectivité de l’ensemble des trois conditions n’a rien de trivialIl existe même des articles qui demandent un solveur de “programmation conique du second ordre” pour traiter ce problème
query planningpar relational algebra, j’ai l’impression, même à vue d’œil, d’obtenir davantage de résultats orientés implémentationLa 14e méthode est la jointure multiple, aussi appelée « worst-case optimal join », même si le nom n’est pas terrible
Au lieu de joindre les tables deux par deux en produisant sans cesse des résultats intermédiaires, cela consiste à joindre ensemble 3 tables ou plus sans résultat intermédiaire
Un billet de blog connexe et une courte vidéo sont disponibles sur https://relational.ai/blog/dovetail-join, et l’article original est ici : https://dl.acm.org/doi/pdf/10.1145/3180143
Je travaille chez RelationalAI, et nous faisons partie, avec quelques autres jeunes entreprises du secteur des bases de données, de ceux qui amènent sur le marché ce nouvel algorithme de jointure étudié dans le monde académique depuis une dizaine d’années
https://justinjaffray.com/a-gentle-ish-introduction-to-worst...
ANDde la jointure devient unNOR, et Tetris exploite ce pointLa borne du pire cas ne devient pas plus serrée que pour les WCOJ sans état / en streaming, mais les données réelles ont souvent des certificats de boîte bien plus petits
Je n’ai pas vu si la jointure Dovetail prend en charge les requêtes récursives, c’est-à-dire du datalog arbitraire où seule la relation de sortie est spécifiée et où le moteur gère lui-même les relations intermédiaires
Je serais curieux de savoir si ce type de requête est pris en charge
Il faudrait plus d’articles de ce genre, qui montrent surtout les subtilités du modèle relationnel aux développeurs au niveau applicatif
L’explication et l’exploration sous l’angle de la programmation fonctionnelle sont elles aussi concises et convaincantes
On dirait qu’ils ont encore raté une occasion d’enseigner le problème N+1
Faire une jointure sur un index non clusterisé reste du N+1, simplement du N+1 sur disque plutôt que du N+1 entre le réseau et le disque
Une jointure interne est un produit cartésien assorti d’une condition
Une jointure interne avec une condition d’égalité génère directement la condition, tandis qu’une condition de jointure non fondée sur l’égalité nécessite une véritable évaluation
Bonne explication. Dire que « la bonne méthode consiste à normaliser les tables » est vrai pour les bases de données transactionnelles, mais dans les entrepôts de données, un certain degré de dénormalisation est largement accepté
Cet exemple de normalisation m’a rappelé l’époque où je concevais des tables en pensant que les clés primaires numériques étaient plus rapides que les chaînes
On se retrouvait alors avec des
iddénués de sens, et il fallait faire des jointures pour obtenir la vraie valeur unique recherchéeUn jour, j’ai compris qu’utiliser la même clé unique dans deux tables pouvait réduire le nombre de jointures, et c’était simple mais efficace
idunique par table. C’est utile pour les logs et cela évite d’avoir à se soucier de la « vraie » clé composée de plusieurs champsEn revanche, je mets un index unique sur les valeurs de chaîne et, plus important encore, j’y attache les contraintes d’intégrité
Une table remplie de chaînes porteuses de sens est bien plus lisible qu’une table remplie d’
idnumériques ou d’UUID