1. Préambule▲
Dans le chapitre précédent, nous avons commencer à décortiquer le simple SELECT. A présent, nous allons nous consacrer aux jointures entre tables.
Autrement dit: comment faire des requêtes portant sur plusieurs tables. La structure de la base de données exemple, ainsi qu'une version des principales bases utilisées sont disponibles dans la page "La base de données exemple"
2. Les jointures ou comment faire des requêtes sur plusieurs tables▲
Les jointures permettent d'exploiter pleinement le modèle relationnel des tables d'une base de données.
Elle sont faites pour mettre en relation deux (ou plus) tables concourant à rechercher la réponse à des interrogations.
Une jointure permet donc de combiner les colonnes de plusieurs tables.
Il existe en fait différentes natures de jointures que nous
expliciterons plus en détail.
Retenez cependant que la plupart des jointures entre
tables s'effectuent en imposant l'égalité des valeurs d'une colonne
d'une table à une colonne d'une autre table.
On parle alors de jointure naturelle ou équi-jointure.
Mais on trouve aussi des jointures d'une table sur elle-même.
On parle alors d'auto-jointure.
De même, il arrive que l'on doive procéder à des
jointures externe, c'est-à-dire joindre une table à une autre, même si
la valeur de liaison est absente dans une table ou l'autre.
Enfin, dans quelques cas, on peut procéder à des
jointures hétérogènes, c'est-à-dire que l'on remplace le critère
d'égalité par un critère d'inégalité ou de différence.
Nous verrons au moins un cas de cette espèce.
Une jointure entre tables peut être mise en oeuvre, soit à l'aide des éléments de syntaxe SQL que nous avons déjà vu, soit à l'aide d'une clause spécifique du SQL, la clause JOIN. Nous allons commencer par voir comment à l'aide du SQL de base nous pouvons exprimer une jointure.
2-1. Premiers essais de jointure▲
Rappel de la syntaxe du SELECT :
SELECT
[
DISTINCT
ou
ALL
]
*
ou liste_de_colonnes FROM
nom_des_tables_ou_des_vues
C'est ici le pluriel de la partie FROM qui change tout!
Tâchons donc de récupérer les n° des téléphones associés aux clients.
Exemple 1 :
Sélectionnez
|
Sélectionnez
|
Cette requête ne possède pas de critère de jointure entre une table
et l'autre.
Dans ce cas, le compilateur SQL calcule le produit
cartésien des deux ensembles, c'est-à-dire qu'à chaque ligne de la
première table, il accole l'ensemble des lignes de la seconde à la
manière d'une "multiplication des petits pains" !
Nous verrons qu'il existe une autre manière, normalisée cette fois, de générer ce produit cartésien.
Mais cette requête est à proscrire.
Dans notre exemple elle génère 17 400 lignes!
Il faut donc définir absolument un critère de jointure.
Dans le cas présent, ce critère est la correspondance entre les colonnes contenant la référence de l'identifiant du client (CLI_ID).
Exemple 2 :
Sélectionnez
|
Sélectionnez
|
Nous n'avons pas fait mieux, car nous avons créé une clause toujours vraie, un peu à la manière de 1 = 1 !
En fait il nous manque une précision : il s'agit de déterminer de quelles tables proviennent les colonnes CLI_ID de droite et de gauche.
Cela se précise à l'aide d'une notation pointée en donnant le nom de la table.
Il est donc nécessaire d'indiquer au compilateur la provenance de chacune des colonnes CLI_ID et donc d'opérer une distinction entre l'une et l'autre colonne.
Ainsi, chaque colonne devra être précédée du nom de la table, suivi d'un point.
Exemple 3 :
Sélectionnez
|
Sélectionnez
|
On tombe ici à 174 enregistrements dans la table !
Mais il existe une autre façon de faire, plus simple encore. On utilise la technique du "surnommage", c'est-à-dire que l'on attribue un surnom à chacune des tables présente dans la partie FROM du SELECT :
Exemple 4 :
Sélectionnez
|
Sélectionnez
|
Ici, la table T_CLIENT a été surnommée "C" et la table T_TELEPHONE "T".
Bien entendu, et comme dans les requêtes monotabulaires on peut poser des conditions supplémentaires de filtrage dans la clause WHERE. Cherchons par exemple les clients dont les numéros de téléphone correspondent à un fax :
Exemple 5 :
Sélectionnez
|
Sélectionnez
|
Le fait de placer comme critère de jointure entre les tables, l'opérateur logique "égal" donne ce que l'on apelle une "équi-jointure".
Comme vous pouvez le constater, le nom du client "BOUVIER" n'apparaît pas. Il n'a pas été "oublié" par le traitement de la requête, mais le numéro de fax de ce client n'est pas présent dans la table T_TELEPHONE. Or le moteur SQL recherche les valeurs de la jointure par égalité. Comme l'ID_CLI de "BOUVIER" n'est pas présent dans la table T_TELEPHONE, il ne peut effectuer la jointure et ignore donc cette ligne. Nous verrons comment réparer cette lacune lorsque nous parlerons des jointures externes.
On peut aussi utiliser les surnoms dans la partie qui suit immédiatement le mot clef SELECT. Ainsi l'exemple 4, peut aussi s'écrire :
Exemple 6 :
Sélectionnez
|
Sélectionnez
|
C'est particulièrement pratique lorsque l'on veut récupérer une
colonne qui se retrouve dans les deux tables, ce qui est souvent le cas
de la (ou les) colonne(s) de clef étrangère qui permet justement
d'assurer la jointure.
Pour joindre plusieurs tables, on peut utiliser le même processus de manière répétitive...
Exemple 7 :
Sélectionnez
|
Sélectionnez
|
De même que nous l'avons vu dans l'exemple 2.4, ne sont visible ici que les lignes clients ayant à la fois, au moins une adresse, un e-mail et au moins un numéro de téléphone. Si nous avions voulu une liste complète des clients avec toutes les coordonnées disponibles, nous aurions du faire une requête externe sur les tables.
2-2. Différents type de jointures (naturelles, équi, non equi, auto, externes, hétérogènes, croisée et union)▲
Lorsque nous étudions le modèle relationnel de notre base de
données exemple nous avons vu que le modèle physique des données,
répercute les clefs des tables maîtres en tant que clefs étrangères des
tables pour lesquelles une jointure est nécessaire.
En utilisant la jointure entre clefs primaires et
clefs secondaires basée sur l'égalité des valeurs des colonnes nous
exécutons ce que les professionnels du SQL appelle une jointure
naturelle.
Il est aussi possible de faire des équi-jointures
qui ne sont pas naturelles, soit par accident (une erreur !), soit par
nécessité.
Il est aussi possible de faire des non
équi-jointures, c'est-à-dire des jointures basée sur un critère
différent de l'égalité, mais aussi des auto-jointures, c'est-à-dire de
joindre la table sur elle-même.
Le cas le plus délicat à comprendre est celui des
jointures externes, c'est-à-dire exiger que le résultat comprenne toutes
les lignes des tables (ou d'au moins une des tables de la
jointure), même s'il n'y a pas correspondance
des lignes entre les différentes tables mise en oeuvre dans la
jointure.
La jointure d'union consiste à ajouter toutes les données des deux tables à condition qu'elles soient
compatibles dans leurs structures.
La jointure croisée permet de faire le produit cartésien des tables.
Enfin on peut effectuer des requêtes hétérogènes, c'est-à-dire de joindre une table d'une base de
données, à une ou plusieurs autres base de données éventuellement même sur des serveurs différents,
voire même sur des serveurs de différents types (par exemple joindre une table T_CLIENT de
la base BD_COMMANDE d'un serveur Oracle à la table T_PROSPECT de la base BD_COMMERCIAL
d'un serveur Sybase !).
Dans la mesure du possible, utilisez toujours un opérateur de jointure normalisé Sql2 (mot clef
JOIN).
- Les jointures faites dans la clause WHERE (ancienne syntaxe de 1986 !) ne permettent pas de faire la distinction de prime abord entre ce qui relève du filtrage et ce qui relève de la jointure.
- Il est à priori absurde de vouloir filtrer dans le WHERE (ce qui restreint les données du résultat) et de vouloir "élargir" ce résultat par une jointure dans la même clause WHERE de filtrage.
- La lisibilité des requêtes est plus grande en utilisant la syntaxe à base de JOIN, en isolant ce qui est du filtrage et de la jointure, mais aussi en isolant avec clarté chaque condition de jointures entre chaque couples de table.
- L'optimisation d'exécution de la requête est souvent plus pointue du fait de l'utilisation du JOIN.
- Lorsque l'on utilise l'ancienne syntaxe et que l'on supprime la clause WHERE à des fins de tests, le moteur SQL réalise le produit cartésiens des tables ce qui revient la plupart du temps à mettre à genoux le serveur !
3. Syntaxe normalisée des jointures▲
Les jointures normalisées s'expriment à l'aide du mot clef JOIN dans la clause FROM. Suivant la nature de la jointure, on devra préciser sur quels critères se base la jointure.
Voici un tableau résumant les différents types de jointures normalisées :
Jointure interne |
Sélectionnez
|
Jointure externe |
Sélectionnez
|
Jointure naturelle |
Sélectionnez
|
Jointure croisée |
Sélectionnez
|
Jointure d'union |
Sélectionnez
|
Nous allons décrire en détail toutes ces jointures.
3-1. Opérateur de jointure naturelle▲
Il existe un opérateur normalisé pour effectué en SQL la jointure naturelle des tables :
SELECT
[
DISTINCT
ou
ALL
]
*
ou liste de colonnes
FROM
table1 NATURAL
JOIN
table2 [
USING
(colonne1
[,
colonne2
...
]
)]
L'opérateur NATURAL JOIN permet d'éviter de préciser les colonnes concernées par la jointure.
Dans ce cas, le compilateur SQL va rechercher dans les 2 tables, les colonnes dont le nom est identique.
Bien entendu, le type de données doit être le même !
NOTA : on veillera au niveau de la modélisation et notamment au niveau du MPD (Modèle Physique de Données) que les noms des colonnes de clefs en relation avec d'autres tables par l'intermédiaires des clefs étrangères soient strictement identiques.
Exemple 8 :
Sélectionnez
|
Sélectionnez
|
Mais cette syntaxe est rarement acceptée par les moteurs SQL actuels !
La partie optionnelle USING permet de restreindre les colonnes concernées, lorsque plusieurs colonnes servent à définir la jointure naturelle. Ainsi la commande SQL :
SELECT
CLI_NOM, TEL_NUMERO
FROM
T_CLIENT
NATURAL
JOIN
T_TELEPHONE
USING
(CLI_ID)
Revient au même que la commande SQL de l'exemple 8.
3-2. Les jointures internes▲
Comme il s'agit de la plus commune des jointures c'est celle qui s'exerce par défaut si on ne précise pas le type de jointure. Après le mot clef ON on doit préciser le critère de jointure.
Reprenons notre exemple de départ :
Exemple 9 :
Sélectionnez
|
Sélectionnez
|
Ou en utilisant le surnommage :
Exemple 10 :
Sélectionnez
|
Sélectionnez
|
Plus pratique à écrire et aussi lisible sinon plus !
NOTA : le mot clef INNER est facultatif. Par défaut l'absence de précision de la nature de la
jointure la fait s'exécuter en jointure interne.
Ainsi on peut reformuler le requête ci-dessus en :
Exemple 11 :
Sélectionnez
|
Sélectionnez
|
3-3. Les jointures externes▲
Les jointures externes sont extrêmement pratiques pour rapatrier le maximum d'informations disponible, même si des lignes de table ne sont pas renseignées entre les différentes tables jointes.
Procédons à l'aide d'un exemple pour mieux comprendre la différence entre une jointure interne et une jointure externe. Nous avons vu à l'exemple 9 que seul les clients dotés d'un numéro de téléphone étaient répertoriés dans la réponse. Ainsi, le client "BOUVIER" était absent.
Exemple 12 :
Sélectionnez
|
Sélectionnez
|
Que faut-il modifier dans la requête pour obtenir une ligne "BOUVIER" avec aucune référence de téléphone
associée dans la réponse ?
Il suffit en fait d'opérer à l'aide d'une jointure externe :
Exemple 13 :
Sélectionnez
|
Sélectionnez
|
ou encore :
SELECT
CLI_NOM, TEL_NUMERO
FROM
T_CLIENT C
LEFT
OUTER
JOIN
T_TELEPHONE T
ON
C.CLI_ID =
T.CLI_ID AND
TYP_CODE IS
NULL
La syntaxe de la jointure externe est la suivante :
SELECT
...
FROM
<
table
gauche>
LEFT
|
RIGHT
|
FULL OUTER
JOIN
<
table
droite 1
>
ON
<
condition de jointure>
[
LEFT
|
RIGHT
|
FULL
OUTER
JOIN
<table
droite
2>
ON
<
condition de jointure 2
>
]
...
Les mots clefs LEFT, RIGHT et FULL indiquent la manière dont le moteur de requête doit effectuer la jointure
externe. Il font référence à la table située à gauche (LEFT) du mot clef JOIN ou à la table située
à droite (RIGHT) de ce même mot clef. Le mot FULL indique que la jointure externe est bilatérale.
Sélectionnez
|
On recherche toutes les valeurs satisfaisant la condition de jointure précisée dans prédicat, puis on rajoute toutes les lignes de la table TGauche qui n'ont pas été prises en compte au titre de la satisfaction du critère. |
Sélectionnez
|
On recherche toutes les valeurs satisfaisant la condition de jointure précisée dans prédicat, puis on rajoute toutes les lignes de la table TDroite qui n'ont pas été prises en compte au titre de la satisfaction du critère. |
Sélectionnez
|
On recherche toutes les valeurs satisfaisant la condition de jointure précisée dans prédicat, puis on rajoute toutes les lignes de la table TGauche et TDroite qui n'ont pas été prises en compte au titre de la satisfaction du critère. |
NOTA : il existe des équivalences entre différentes expressions logiques à base de jointures externes.
Les principales sont :
- la jointure externe droite peut être obtenue par une jointure externe gauche dans laquelle on inverse l'ordre des tables.
- la jointure externe bilatérale peut être obtenue par la combinaison de deux jointures externes unilatérales avec l'opérateur ensemblistes UNION.
Remplacement d'une jointure externe droite par une jointure externe gauche.
L'équivalent logique de :
Exemple 14 :
SELECT
CLI_NOM, TEL_NUMERO
FROM
T_CLIENT C
RIGHT
OUTER
JOIN
T_TELEPHONE T
ON
C.CLI_ID =
T.CLI_ID
WHERE
TYP_CODE =
'
FAX
'
est :
SELECT
CLI_NOM, TEL_NUMERO
FROM
T_TELEPHONE T
LEFT
OUTER
JOIN
T_CLIENT C
ON
C.CLI_ID =
T.CLI_ID
WHERE
TYP_CODE =
'
FAX
'
Remplacement d'un FULL OUTER JOIN avec jointures externes gauche et droite :
L'équivalent logique de ...
Exemple 15 :
SELECT
CLI_NOM, TEL_NUMERO
FROM
T_CLIENT C
FULL OUTER
JOIN
T_TELEPHONE T
ON
C.CLI_ID =
T.CLI_ID
WHERE
TYP_CODE =
'
FAX
'
est :
SELECT
CLI_NOM, TEL_NUMERO
FROM
T_CLIENT C
LEFT
OUTER
JOIN
T_TELEPHONE T
ON
C.CLI_ID =
T.CLI_ID
WHERE
TYP_CODE =
'
FAX
'
UNION
SELECT
CLI_NOM, TEL_NUMERO
FROM
T_CLIENT C
RIGTH OUTER
JOIN
T_TELEPHONE T
ON
C.CLI_ID =
T.CLI_ID
WHERE
TYP_CODE =
'
FAX
'
Remplacement d'un FULL OUTER JOIN avec jointures externes gauche uniquement :
L'équivalent logique de ...
Exemple 16 :
SELECT
CLI_NOM, TEL_NUMERO
FROM
T_CLIENT C
FULL OUTER
JOIN
T_TELEPHONE T
ON
C.CLI_ID =
T.CLI_ID
WHERE
TYP_CODE =
'
FAX
'
est :
SELECT
CLI_NOM, TEL_NUMERO
FROM
T_CLIENT C
LEFT
OUTER
JOIN
T_TELEPHONE T
ON
C.CLI_ID =
T.CLI_ID
WHERE
TYP_CODE =
'
FAX
'
UNION
SELECT
CLI_NOM, TEL_NUMERO
FROM
T_TELEPHONE T
LEFT
OUTER
JOIN
T_CLIENT C
ON
C.CLI_ID =
T.CLI_ID
WHERE
TYP_CODE =
'
FAX
'
3-4. Différence entre jointure externe et jointure interne▲
Pour bien comprendre la distinction entre les jointures internes et externes, nous devons consacrer quelques instants à aborder des problèmes de logique ensembliste sous un oeil pragmatique.
3-4-1. L'hypothèse du monde clos▲
Les jointures externes sont extrêmement pratiques pour rapatrier le maximum d'informations disponible, même si des lignes de table ne sont pas renseignées entre les différentes tables jointes.
Sans le savoir, nous faisons assez systématiquement l'hypothèse du monde clos. c'est-à-dire que nous
considérons que l'absence d'information, n'est pas une information. Si vous demandez à une secrétaire
de vous communiquer les coordonnées des clients qui sont domiciliés à Paris, elle vous donnera
une liste où figurera autant de fois le nom "Paris" qu'il y a de clients dans la liste, et ceci paraît
bien normal ! Sauf que, comme l'aurait fait tout un chacun, votre secrétaire a fait l'hypothèse
du monde clos sans le savoir en présumant que les clients pour lesquels l'adresse n'est pas renseignée
ne sont pas domiciliés à PARIS !
C'est cela l'hypothèse du monde clos : considérer que l'absence d'information doit être synonyme
de critère de discrimination... La jointure externe permet de contrer l'hypothèse du monde
clos en considérant qu'en cas d'absence de jointure entre une table et l'autre, on ne supprime
par pour autant l'information.
3-4-2. Mécanisme en jeu▲
Lorsqu'une ligne d'une table figurant dans une jointure n'a pas de correspondance dans les autres tables, le critère d'équi-jointure n'est pas satisfait et la ligne est rejetée. C'est la jointure interne. Au contraire, la jointure externe permet de faire figurer dans le résultat les lignes satisfaisant la condition d'équi-jointure ainsi que celles n'ayant pas de correspondances.
Ainsi, si je veux contacter tous mes clients, quelque soit le mode de contact que je veux utiliser dans le cadre d'une campagne publicitaire, j'ai intérêt à obtenir une réponse contenant tous les clients, même ceux qui n'ont pas de téléphone, d'e-mail ou d'adresse.
Exemple 17 :
Sélectionnez
|
Sélectionnez
|
NOTA : Sur certains moteurs SQL la jointure bilatérale externe (FULL OUTER JOIN) s'exprime :
SELECT
colonnes
FROM
TGauche FULL JOIN
TDroite ON
condition de jointure
D'anciennes syntaxes permettent de faire des jointures externes unilatérale. Par exemple, il n'est pas
rare de rencontrer les syntaxes suivantes :
SELECT
colonnes
FROM
table_1 t1, table_2 t2
WHERE
t1.id1 *
=
t2.id2
ou encore :
SELECT
colonnes
FROM
table_1 t1, table_2 t2
WHERE
t1.id1 (+
)=
t2.id2
Elles sont bien évidemment à proscrire si la syntaxe SQL 2 est disponible !
3-4-3. Discussion sur la jointure externe▲
La jointure externe est rarement bien comprise du premier coup. Si je vous propose de lire cette discussion qui a eût lieu sur un forum Internet, c'est parce quelle permet de mieux la comprendre.
Sélectionnez
|
Sélectionnez
|
Sélectionnez
|
Sélectionnez
|
Sélectionnez
|
3-5. La jointure croisée▲
La jointure croisée n'est autre que le produit cartésien de deux tables. Rappelons que le produit cartésien de deux ensembles n'est autre que la multiplication généralisée. Dans le cas des tables, c'est le fait d'associer à chacune des lignes de la première table, toutes les lignes de la seconde. Ainsi, si la première table compte 267 lignes et la seconde 1214, on se retrouve avec un résultat contenant 324 138 lignes. Bien entendu, s'il n'y a aucun doublon dans les tables, toutes les lignes du résultat seront aussi uniques.
La jointure croisée peut s'écrire de deux manières différentes :
-
à l'aide de l'opérateur normalisé :
Sélectionnez
SELECT
colonnesFROM
table_1 CROSSJOIN
table_2 -
ou à l'aide d'une clause FROM simplifiée :
Sélectionnez
SELECT
colonnesFROM
table_1, table_2
Ce qui est certainement l'expression la plus minimaliste de tous les ordres SELECT du SQL !
Nous voudrions savoir si notre table des tarifs de chambre (TJ_TRF_CHB) est complète, c'est-à-dire si l'on a bien toutes les chambres (T_CHAMBRE) pour toutes les dates de début de tarif (T_TARIF) :
Exemple 18 :
Sélectionnez
|
Sélectionnez
|
En comparant rapidement le contenu des deux tables on peut s'assurer que tous les tarifs ont bien été
renseignés.
Avec la syntaxe normalisée, cette requête s'écrit :
Exemple 19 :
Sélectionnez
|
Sélectionnez
|
et donne le même résultat !
3-6. La jointure d'union▲
La jointure d'union, permet de faire l'union de deux tables de structures quelconque. Elle s'exprime qu'à l'aide de l'opérateur normalisé SQL2 suivant :
SELECT
colonnes
FROM
table_1 UNION
JOIN
table_2
Effectuons par exemple la jointure d'union entre les tables T_TITRE ("M.", "Mme.", "Melle." ) et T_TYPE ("FAX", "GSM", "TEL") :
Exemple 20 :
Sélectionnez
|
Sélectionnez
|
En fait c'est comme si l'on avait listé la première table, puis la seconde en évitant toute colonne commune
et compléter les espaces vides des valeurs NULL.
Mais cette jointure est très rarement implantée dans les moteurs SQL.
NOTA : si l'opérateur UNION JOIN n'est pas présent dans votre moteur, vous pouvez le fabriquer comme suit, car elle (l'union) peut être facilement remplacée par un autre ordre SQL presque aussi simple :
SELECT
*
FROM
<
table
gauche>
FULL OUTER
JOIN
<
table
droite>
ON
<
critère>
Où la condition <critère> est n'importe quel prédicat valant toujours faux comme "1=2".
En dehors du fait de linéariser des tables hétérogènes, il est franchement permis de douter de l'utilité d'un tel opérateur.
4. Nature des conditions de jointures▲
Nous allons maintenant analyser les différentes jointures basées sur la nature des conditions pour bien les distinguer.
4-1. Équi-jointure▲
L'équi-jointure consiste à opérer une jointure avec une condition d'égalité. Cette condition d'égalité
dans la jointure peut ne pas porter nécessairement sur les clefs (primaires et étrangères).
Recherchons par exemple les clients dont le nom est celui d'une ville contenu dans la table des
adresses :
Exemple 21 :
Sélectionnez
|
Sélectionnez
|
Nous avons donc bien réalisé une équi-jointure, mais elle n'est pas naturelle parce qu'elle ne repose
pas sur les clefs des tables.
Bien entendu, il existe un opérateur normalisé SQL 2 permettant de traiter le cas de l'équi-jointure :
SELECT
[
DISTINCT
ou
ALL
]
*
ou liste de colonnes
FROM
table1 [
INNER
]
JOIN
table2 ON
condition de jointure
Le mot clef INNER n'étant pas obligatoire, mais voulant s'opposer aux mot clefs OUTER, UNION et CROSS.
Ainsi, la requête précédente, s'écrit à l'aide de cette syntaxe :
Exemple 22 :
Sélectionnez
|
Sélectionnez
|
4-2. Non équi-jointure▲
Il s'agit là d'utiliser n'importe quelle condition de jointure entre deux tables, exceptée la stricte égalité. Ce peuvent être les conditions suivantes :
> | supérieur |
>= | supérieur ou égal |
< | inférieur |
<= | inférieur ou égal |
<> | différent de |
IN | dans un ensemble |
LIKE | correspondance partielle |
BETWEEN ... AND ... | entre deux valeurs |
EXISTS | dans une table |
En règle générale ou trouve des non équi-jointures dans le cadre de comparaisons temporelles ou de mesures
physiques. Par exemple on pourrait rechercher une pièce mécanique dans un stock qui soit de même
nature ou de même fonction qu'une pièce donnée, mais plus légère.
Nous voulons obtenir les factures qui ont été émises avant que le prix des petits déjeuners n'atteigne 6 €.
Exemple 23 :
Sélectionnez
|
Sélectionnez
|
NOTA : pour récupérer toutes les colonnes d'une table, on peut utiliser l'opérateur * suffixé
par le nom de table, comme nous l'avons fait ici pour la table des factures.
Si notre table des tarifs avait été organisée par tranches, comme ceci :
TRF_DATE_DEBUT TRF_DATE_FIN TRF_TAUX_TAXES TRF_PETIT_DEJEUNE
-------------- ------------ -------------- -----------------
1999-01-01 1999-08-31 18,60 6,00 E
1999-09-01 1999-12-31 20,60 7,00 E
2000-01-01 2000-08-31 20,60 8,00 E
2000-09-01 2000-12-31 20,60 9,00 E
2001-01-01 2001-12-31 20,60 10,00 E
alors,récupérer le tarif des chambres pour chacune des dates du planning devient un exercice très simple :
Exemple 24 :
Sélectionnez
|
Sélectionnez
|
Nous avons donc à nouveau un exemple remarquable de non equi-jointure.
Constatons que la colonne TRF_DATE_FIN de cette nouvelle version de la table des tarifs
implique une redondance de l'information. En effet, cette date de fin est déductible de la date de
début de la ligne qui contient la date immédiatement postérieure avec un jour de moins. De plus
le problème induit par cette organisation des données fait qu'il faut obligatoirement définir une
date de fin des tarifs, même dans le futur, sinon certaines tarifications ne pourront être établies
par cette requête.
Il ne s'agit donc pas d'une modélisation correcte !
4-3. Auto-jointure▲
Le problème consiste à joindre une table à elle-même. Il est assez fréquent que l'on ait besoin de telles auto-jointures car elle permettent notamment de modéliser des structures de données complexes comme des arbres. Voici quelques exemples de relation nécessitant une auto-jointure de tables :
- dans une table des employées, connaître le supérieur hiérarchique de tout employé
- dans une table de nomenclature savoir quels sont les composants nécessaires à la réalisation d'un module, ou les modules nécessaires à la réalisation d'un appareil
- dans une table de personnes, retrouver l'autre moitié d'un couple marié.
La représentation d'une telle jointure dans le modèle de données, se fait par le rajout d'une colonne
contenant une pseudo clef étrangère basée sur le clef de la table.
Dans ce cas, une syntaxe possible pour l'auto-jointure est la suvante :
SELECT
[
DISTINCT
ou
ALL
]
*
ou liste de colonnes
FROM
laTable t1
INNER
JOIN
laTable t2
ON
t1.laClef =
t2.laPseudoClefEtrangère
C'est l'exemple typique ou l'utilisation de surnoms pour les tables est obligatoire, sinon il y a risque de confusion pour le moteur SQL.
Pour donner un exemple concret à nos propos nous allons modéliser le fait qu'une chambre puisse communiquer
avec une autre (par une porte). Dès lors, le challenge est de trouver quelles sont les chambres
qui communiquent entre elles par exemple pour réaliser une sorte de suite. Pour ce faire, nous
allons ajouter à notre table des chambres une colonne de clef étrangère basée sur la clef de la
table.
Dans ce cas, cette colonne doit obligatoirement accepter des valeurs nulles !
Voici l'ordre SQL pour rajouter la colonne CHB_COMMUNIQUE dans la table T_CHAMBRE :
ALTER
TABLE
T_CHAMBRE ADD
CHB_COMMUNIQUE INTEGER
Alimentons là de quelques valeurs exemples en considérant que la 7 communique avec la 9 et la 12 avec la 14 :
UPDATE
T_CHAMBRE SET
CHB_COMMUNIQUE =
9
WHERE
CHB_ID =
7
UPDATE
T_CHAMBRE SET
CHB_COMMUNIQUE =
7
WHERE
CHB_ID =
9
UPDATE
T_CHAMBRE SET
CHB_COMMUNIQUE =
12
WHERE
CHB_ID =
14
UPDATE
T_CHAMBRE SET
CHB_COMMUNIQUE =
14
WHERE
CHB_ID =
12
Pour formuler la recherche de chambres communiquantes, il suffit de faire la requête suivante :
Exemple 25 :
Sélectionnez
|
Sélectionnez
|
où la table T_CHAMBRE figure deux fois par l'entremise de deux surnommages différents c1 et c2.
Notons que cette présentation n'est pas pratique car elle dédouble le nombre de couples de chambres communiquantes, donnant l'impression qu'il y a 4 couples de chambres communiquantes ce qui est faux. Il manque juste un petit quelque chose pour que cette requête soit parfaite. Il suffit en effet de ne retenir les solutions que pour des identifiants inférieurs ou supérieur d'une table par rapport à l'autre :
Exemple 26 :
Sélectionnez
|
Sélectionnez
|
4-4. La jointure hétérogène▲
Une jointure hétérogène consiste à joindre dans une même requête, des tables provenant de bases de données différentes, voire de serveurs de données différents. Un tel type de jointure n'est possible que :
- entre deux bases d'un même serveur : que si le SQL du serveur le permet (par exemple deux bases Oracle, deux bases SQL Server, etc.)
- entre deux bases de deux serveurs différents : qu'en passant par un outil tiers de type "middleware" (par exemple une entre une base Oracle et une base Sybase). L'un des rares middleware a faire ce type de jointure est le BDE (Borland Database Engine) d'Inprise.
Dans ces deux cas, il faut bien vérifier la compatibilité des types car il se peut que des résultats surprenants apparaissent notamment dans le traitement des nombre réels.
Pour effectuer de telles requêtes, la syntaxe est toujours spécifique au moteur employé. Avec le BDE le niveau de SQL est SQL 2, cantonné aux fonctions de base.
Voici un exemple de requête entre deux serveur à l'aide du BDE :
Exemple 27 :
Sélectionnez
|
Sélectionnez
|
En fait le BDE se sert d'alias permettant de définir à la fois le serveur et la base concerné. Ici les
alias sont : ":ORACLE_CLIBD:" et ":SYBASE_TELBD:".
5. Récapitulatif des jointures normalisées▲
5-1. Terminologie et syntaxe des jointures▲
- Jointure naturelle : la jointure s'effectue sur les colonnes communes, c'est-à-dire celles de
même nom et type :
Le mot clef USING permet de restreindre les colonnes communes à prendre en considération.Sélectionnez
SELECT
colonnesFROM
table1NATURAL
JOIN
table2[
USING
col1,
col2
...
]
[
WHERE
prédicat
]
...
- Jointure interne : la jointure s'effectue entre les tables sur les colonnes précisées dans la
condition de jointure :
Sélectionnez
SELECT
colonnesFROM
table1 t1[
INNER
]
JOIN
table2 t2ON
condition[
WHERE
prédicat
]
...
- Jointure externe : la jointure permet de récupérer les lignes des tables correspondant au critère
de jointure, mais aussi celle pour lesquelles il n'existe pas de correspondances.
Sélectionnez
SELECT
colonnesFROM
table1 t1[
RIGHT
OUTER
|
LEFT
OUTER
|
FULL
OUTER
]
JOIN
table2 t2ON
condition[
WHERE
prédicat
]
...- RIGHT OUTER : la table à droite de l'expression clef "RIGHT OUTER" renvoie des lignes sans correspondance avec la table à gauche.
- LEFT OUTER : la table à gauche de l'expression clef "LEFT OUTER" renvoie des lignes sans correspondance avec la table à droite.
- FULL OUTER : les deux tables renvoient des lignes sans correspondance entre elles.
- Jointure croisée : la jointure effectue le produit cartésien (la "multiplication") des deux tables.
Il n'y a pas de condition.SélectionnezSELECT
colonnesFROM
table1 t1 CROSSJOIN
table2 t2[
WHERE
prédicat
]
...
- Jointure d'union : la jointure concatène les tables sans aucune correspondances de colonnes.
Il n'y a pas de critère de jointure.Sélectionnez
SELECT
colonnesFROM
table1UNION
JOIN
table2
Si votre SGBDR n'implémente pas la jointure externe droite, inversez l'ordre des tables et faire une jointure externe gauche lorsque cela est possible.
5-2. Arbre de jointure▲
La jointure de multiple tables peut se représenter sous la forme d'un arbre. Cet arbre possède donc une
racine, c'est la table principale, celle d'où l'on veut que l'information parte. Elle possède aussi
des feuilles, c'est-à-dire des tables d'entités. Les tables situées entre la racine et les feuilles,
sont souvent des tables de jointure, possédant en principe deux clef étrangères. Dans le principe
toute table de jointure devrait être un noeud de l'arbre.
La représentation arborescente d'une jointure est un excellent moyen pour visualiser si la clause
de jointure de votre requête est à priori correcte. En effet, une référence circulaire dans la clause
de jointure ne peut pas être représentée sous la forme d'un arbre et il y a fort à parier que la
requête soit incorrecte.
Voici par exemple la requête qui "met à plat", la base hotel :
SELECT
*
FROM
T_CLIENT CLI --
le
client
(racine
de
l'arbre)
JOIN
T_ADRESSE ADR --
adresse,
table
d'entité
(feuille
de
l'arbre)
ON
CLI.CLI_ID =
ADR.CLI_ID
JOIN
T_TITRE TIT --
titre,
table
d'entité
(feuille
de
l'arbre)
ON
CLI.TIT_CODE =
TIT.TIT_CODE
JOIN
T_EMAIL EML --
mail,
table
d'entité
(feuille
de
l'arbre)
ON
CLI.CLI_ID =
EML.CLI_ID
JOIN
T_TELEPHONE TEL --
téléphone,
table
d'entité
servant
de
jointure
(noeud
dans
l'arbre)
ON
CLI.CLI_ID =
TEL.CLI_ID
JOIN
T_TYPE TYP --
type
de
téléphone,
table
d'entité
(feuille
de
l'arbre)
ON
TEL.TYP_CODE =
TYP.TYP_CODE
JOIN
TJ_CHB_PLN_CLI CPC --
table
de
jointure
(noeud
dans
l'arbre)
ON
CLI.CLI_ID =
CPC.CLI_ID
JOIN
T_PLANNING PLN --
date
du
planning,
table
d'entité
(feuille
de
l'arbre)
ON
CPC.PLN_JOUR =
PLN.PLN_JOUR
JOIN
T_CHAMBRE CHB --
chambre,
table
d'entité
servant
de
jointure
(noeud
dans
l'arbre)
ON
CPC.CHB_ID =
CHB.CHB_ID
JOIN
TJ_TRF_CHB TC --
table
de
jointure
(noeud
dans
l'arbre)
ON
CHB.CHB_ID =
TC.CHB_ID
JOIN
T_TARIF TRF --
tarif,
table
d'entité
(feuille
de
l'arbre)
ON
TC.TRF_DATE_DEBUT =
TRF.TRF_DATE_DEBUT
JOIN
T_FACTURE FAC --
facture,
table
d'entité
servant
de
jointure
ON
CLI.CLI_ID =
FAC.CLI_ID
JOIN
T_LIGNE_FACTURE LIF --
ligne
de
facture,
table
d'entité
(feuille
de
l'arbre)
ON
FAC.FAC_ID =
LIF.FAC_ID
JOIN
T_MODE_PAIEMENT PMT --
mode
de
paiement,
table
d'entité
(feuille
de
l'arbre)
ON
FAC.PMT_CODE =
PMT.PMT_CODE
Correctement indenté on distingue déjà la structure arborescente. On peut la mettre en évidence en supprimant tout ce qui n'est pas un nom de table :
T_CLIENT CLI --
le
client
(racine
de
l'arbre)
T_ADRESSE ADR --
adresse,
table
d'entité
(feuille
de
l'arbre)
T_TITRE TIT --
titre,
table
d'entité
(feuille
de
l'arbre)
T_EMAIL EML --
mail,
table
d'entité
(feuille
de
l'arbre)
T_TELEPHONE TEL --
téléphone,
table
d'entité
servant
de
jointure
(noeud
dans
l'arbre)
T_TYPE TYP --
type
de
téléphone,
table
d'entité
(feuille
de
l'arbre)
TJ_CHB_PLN_CLI CPC --
table
de
jointure
(noeud
dans
l'arbre)
T_PLANNING PLN --
date
du
planning,
table
d'entité
(feuille
de
l'arbre)
T_CHAMBRE CHB --
chambre,
table
d'entité
servant
de
jointure
(noeud
dans
l'arbre)
TJ_TRF_CHB TC --
table
de
jointure
(noeud
dans
l'arbre)
T_TARIF TRF --
tarif,
table
d'entité
(feuille
de
l'arbre)
T_FACTURE FAC --
facture,
table
d'entité
servant
de
jointure
T_LIGNE_FACTURE LIF --
ligne
de
facture,
table
d'entité
(feuille
de
l'arbre)
T_MODE_PAIEMENT PMT --
mode
de
paiement,
table
d'entité
(feuille
de
l'arbre)
Par cette indentation, il est facile de repérer les jointures entre les tables.
6. Note importante▲
Les jointures ne sont pas la seule manière de mettre en realtion différentes tables au sein d'une même requête SQL. On peut aussi joindre plusieurs tables à l'aide des sous-requêtes ainsi qu'à l'aide des opérateurs ensemblistes que nous allons voir aux deux prochains chapitres.
7. Résumé▲
Voici les différences entre les moteurs des bases de données :
Paradox | Access | PostGreSQL | Sybase | SQL Server 7 | Oracle 8 | DB2 (400) | |
---|---|---|---|---|---|---|---|
INNER JOIN | Oui | Oui | Oui | Oui | Oui | Non (1) | Oui |
OUTER JOIN | LEFT, RIGHT, FULL | LEFT, RIGHT | LEFT, RIGHT, FULL | LEFT, RIGHT, FULL | LEFT, RIGHT, FULL | Non (1) | LEFT (4) |
UNION JOIN | Non (2) | Non | Non | Non (2) | Non (2) | Non (1) (2) | Non |
CROSS JOIN | Non (3) | Non (3) | Oui | Non (3) | Oui | Non (1) (3) | Oui |
(1) Oracle ne connaît toujours pas le JOIN (ça fait quand même plus de dix ans de retard pour cet éditeur
pionnier qui semble s'endormir sur ses lauriers). Il faut donc utiliser une syntaxe propriétaire.
Exception : la version 9 supporte enfin les jointures normalisées.
(2) Possible avec un FULL OUTER JOIN
(3) Possible avec l'ancienne syntaxe sans précision de critère WHERE
(4) de plus IBM DB2 (400) dispose d'un très intéressant "exception join" equivalent à :
SELECT
*
FROM
tablegauche
LEFT
OUTER
JOIN
tabledroite
ON
références de jointure
WHERE
tabledroite.clef IS
NULL