Première rédaction de cet article le 6 mai 2009
Je ne vais pas faire le Nième tutoriel sur les jointures SQL (pour cela, je recommande l'excellent article du Wikipédia anglophone), j'essaie juste de documenter deux ou trois choses de base sur elles, pour m'en souvenir la prochaine fois. Si ça peut servir à d'autres...
Une jointure, c'est... joindre deux tables, en général avec un critère de sélection. L'idée de base est de générer des tuples qui combinent les tuples des deux bases. Ce n'est donc pas une opération ensembliste, contrairement à ce qu'à écrit Jeff Atwood.
Avant de commencer, je crée quelques tables pour illustrer la question, une table de personnes et une de livres (le code SQL complet, testé avec PostgreSQL est disponible en ligne) :
CREATE TABLE Authors ( id SERIAL UNIQUE NOT NULL, name TEXT NOT NULL); CREATE TABLE Books ( id SERIAL UNIQUE NOT NULL, title TEXT NOT NULL, author INTEGER REFERENCES Authors(id));
Bien sûr, ce schéma de données est ultra-simplifié. Par exemple, il ne
permet pas de représenter un livre qui a plusieurs auteurs. Mais peu
importe, c'est pour faire des exemples. Le point important à noter est
qu'un « auteur » n'est pas forcément référencé par un livre et qu'un
livre peut ne pas avoir d'auteur connu (la colonne
author
peut être
NULL
).
Voici les données, avec des livres que j'ai apprécie (comme « La horde du contrevent » ou les livres de Fred Vargas) :
essais=> SELECT * FROM Authors; id | name ----+--------------------- 1 | Stéphane Bortzmeyer 2 | Fred Vargas 3 | Ève Demazière 4 | Alain Damasio (4 rows) essais=> SELECT * FROM Books; id | title | author ----+-----------------------------------------+-------- 1 | Les cultures noires d'Amérique Centrale | 3 2 | La horde du contrevent | 4 3 | Pars vite et reviens tard | 2 4 | L'homme à l'envers | 2 5 | Bible | (5 rows)
(Cherchez le livre sans auteur connu et la personne qui n'a pas écrit de livre.)
Il existe deux grands types de jointures, la jointure interne
(INNER JOIN
) et l'externe (OUTER
JOIN
). S'il existe toujours un lien entre les deux tables
(si tous les livres ont un auteur et si tous les auteurs sont
référencés depuis au moins un livre), il n'existe pas de différence
entre les deux types. Mais ce n'est pas le cas en général.
La jointure interne s'écrit avec le mot-clé INNER
JOIN
et la condition ON
(l'ensemble des requêtes de jointure
est disponible en ligne, tous les tests
ont été faits avec PostgreSQL 8.3) :
essais=> SELECT name, title FROM Books INNER JOIN Authors ON Books.author = Authors.id; name | title ---------------+----------------------------------------- Ève Demazière | Les cultures noires d'Amérique Centrale Alain Damasio | La horde du contrevent Fred Vargas | Pars vite et reviens tard Fred Vargas | L'homme à l'envers (4 rows)
(Le INNER
est facultatif, la jointure interne est
la jointure par défaut.) Si les deux colonnes sur lesquelles se fait
la jointure ont le même nom, on peut utiliser
USING
au lieu de ON
.
Les jointures internes s'écrivent plus fréquemment (mais c'est une question de goût) avec l'ancienne syntaxe :
essais=> SELECT name, title FROM Books, Authors WHERE Books.author = Authors.id; name | title ---------------+----------------------------------------- Ève Demazière | Les cultures noires d'Amérique Centrale Alain Damasio | La horde du contrevent Fred Vargas | Pars vite et reviens tard Fred Vargas | L'homme à l'envers (4 rows)
Dans les deux cas (c'était juste une différence de syntaxe), le
résultat est le même et n'inclus pas les livres sans auteur connu, ni
les « auteurs » qui n'ont pas écrit de livre. Une jointure interne
ne sélectionne que les tuples qui répondent à la condition de jointure
(le ON
, dans la nouvelle syntaxe).
Et les jointures externes ? Au contraire des internes, elles
produisent également des tuples où la condition de jointure n'est pas
remplie. Un mot clé LEFT
ou
RIGHT
est obligatoire. Une jointure gauche garde
les tuples de la table indiquée à gauche et vous vous doutez de ce que
fera une jointure droite. On peut facilement transformer l'une en
l'autre, A LEFT OUTER JOIN B
est la même chose
que B RIGHT OUTER JOIN A
. Je ne montrerai donc que les
jointures externes à gauche :
essais=> SELECT name, title FROM Books LEFT OUTER JOIN Authors ON Books.author = Authors.id; name | title ---------------+----------------------------------------- Ève Demazière | Les cultures noires d'Amérique Centrale Alain Damasio | La horde du contrevent Fred Vargas | Pars vite et reviens tard Fred Vargas | L'homme à l'envers | Bible (5 rows)
On voit désormais le livre sans auteur. Si la colonne
name
vide gène :
essais=> SELECT CASE WHEN name IS NULL THEN 'Unknown author' ELSE name END, title FROM Books LEFT OUTER JOIN Authors ON Books.author = Authors.id; name | title ----------------+----------------------------------------- Ève Demazière | Les cultures noires d'Amérique Centrale Alain Damasio | La horde du contrevent Fred Vargas | Pars vite et reviens tard Fred Vargas | L'homme à l'envers Unknown author | Bible (5 rows)
Et si on veut garder, non pas les livres sans auteur mais les personnes sans livres, on utilise une jointure externe à droite ou bien, tout simplement, on inverse les tables :
essais=> SELECT name, title FROM Authors LEFT OUTER JOIN Books ON Books.author = Authors.id; name | title ---------------------+----------------------------------------- Stéphane Bortzmeyer | Fred Vargas | Pars vite et reviens tard Fred Vargas | L'homme à l'envers Ève Demazière | Les cultures noires d'Amérique Centrale Alain Damasio | La horde du contrevent (5 rows)
Donc, contrairement à ce que présente l'article d'Atwood déjà cité,
INNER JOIN
n'est pas une
intersection et OUTER JOIN
n'est pas une
union (SQL a des
opérateurs pour ces opérations ensemblistes mais je ne les utilise pas
ici). Les jointures créent des nouveaux tuples, elles ne sélectionnent
pas des tuples existants.
Et si on veut aussi bien les livres sans auteurs que les gens qui
n'ont pas écrit de livre ? C'est le rôle de la jointure externe
complète, FULL OUTER JOIN
:
essais=> SELECT name, title FROM Books FULL OUTER JOIN Authors ON Books.author = Authors.id; name | title ---------------------+----------------------------------------- Stéphane Bortzmeyer | Fred Vargas | Pars vite et reviens tard Fred Vargas | L'homme à l'envers Ève Demazière | Les cultures noires d'Amérique Centrale Alain Damasio | La horde du contrevent | Bible (6 rows)
Et enfin, pour un exemple réel, emprunté à DNSmezzo, une jointure externe
un peu plus compliquée. La table DNS_packets
contient des paquets DNS, la table DNS_types
, la
correspondance entre le numéro de type d'enregistrement, contenu dans
la requête, et des chaînes de caractères mnémoniques comme NAPTR ou SRV. On fait une sous-requête
SQL pour ne garder que les paquets utiles, puis une jointure externe
(car certains paquets contiennent des requêtes pour des types qui ne
sont pas enregistrés dans la base IANA et
sont donc absents de la table DNS_types
) :
dnsmezzo=> SELECT (CASE WHEN type IS NULL THEN qtype::TEXT ELSE type END), meaning, count(Results.id) AS requests FROM (SELECT id, qtype FROM dns_packets WHERE (file=5 or file=13) AND query) AS Results LEFT OUTER JOIN DNS_types ON qtype = value GROUP BY qtype, type, meaning ORDER BY requests desc; type | meaning | requests --------+----------------------------------------+---------- MX | mail exchange | 983180 A | a host address | 847228 AAAA | IP6 Address | 129656 NS | an authoritative name server | 13583 SOA | marks the start of a zone of authority | 10562 TXT | text strings | 10348 255 | | 9125 38 | | 8440 SRV | Server Selection | 3300 SPF | | 677 PTR | a domain name pointer | 384 CNAME | the canonical name for an alias | 351 DNSKEY | DNSKEY | 323 0 | | 39 26226 | | 11 NAPTR | Naming Authority Pointer | 11 HINFO | host information | 7 NSEC | NSEC | 7 8808 | | 1 14184 | | 1 3840 | | 1 54312 | | 1 13203 | | 1 (23 rows)
Version PDF de cette page (mais vous pouvez aussi imprimer depuis votre navigateur, il y a une feuille de style prévue pour cela)
Source XML de cette page (cette page est distribuée sous les termes de la licence GFDL)