Jointures

Jointures

Nous avons déjà vu des jointures de table lors de l'utilisation de la clause from avec plusieurs sources. Notamment, nous avons vu que la jointure se fait via un produit cartésien, limité par les critÚres qu'on précise dans la clause where.

Si on fait plusieurs jointures, la clause where devient longue et difficile à gérer. En plus, contrÎler finement le type de jointure demande une attention exacte sur la clause where.

select 
  a.name as Action, 
  c.name as Customer 
from 
  action as a, 
  customer as c, 
  action_customer as ac 
where 
  a.actionId = ac.actionId and 
  ac.customerId = c.customerId;

SQL nous facilite cette tĂąche avec les clauses type join, qui exĂ©cutent les jointures les plus utilisĂ©s, et nous permet de les exprimer de façon plus claire. La requĂȘte dessus peut ĂȘtre exprimĂ© avec, par exemple, des inner join :

select a.name as Action, c.name as Customer 
from 
  action a
inner join
  action_customer ac on a.actionId = ac.actionId 
inner join
  customer c on ac.customerId = c.customerId;

Avec cette formulation, la phrase est plus lisible, car il est explicitement marqué qu'on est en train de joindre deux tables, et avec quel critÚre.

Il y a plusieurs types de join Ă  notre disposition.

natural join

Il est possible de joindre deux tables en utilisant les attributs qui partagent le mĂȘme nom. Cela veut dire qu'on n'a pas besoin de la clause on, parce que les critĂšres de jointure sont implicites.

Par exemple :

select a.name, ac.amount
from action a
natural join action_customer ac;

Ici, les attributs en commun entre les tables action et action_customer sont utilisés comme critÚres de jointure.

Mais attention ! Nous espĂ©rons que la jointure se fait via le lien Ă©tabli via la clĂ© Ă©trangĂšre (actionId), mais ce n'est pas le cas avec natural join. Il y a en effet, plusieurs colonnes avec le mĂȘme nom, notamment actionId, createdAtUtc, createdByUtc, createdBy, updatedBy. Le natural join va faire la jointure des lignes seulement si tous les attributs sont identiques entre les deux tables.

Prenons l'exemple de deux tables simples qui représente un pays et une équipe de football.

pays

idpaysnomlangue

1

FR

France

français

2

ZA

Afrique du Sud

anglais

3

UK

Grande Bretagne

anglais

equipe

paysequipelangue

FR

Les bleus

français

ZA

Bafana bafana

xhosa

Et une requĂȘte de type natural join :

select *
from pays
natural join equipe;

Le résultat est :

idpaysnomlangueequipe

1

FR

France

français

Les bleus

On aurait attendu 2 lignes dans la jointure (pour les deux pays en commun, ZA et FR). En revanche, on voit que la requĂȘte nous retourne une seule ligne, car les attributs pays et langue sont utilisĂ©s dans la jointure.

Natural join, donc, n'est quasiment jamais utilisé, puisque le risque d'erreur est trop grand.

inner join

Un inner join est typiquement le type de jointure que nous avons utilisés implicitement avec nos clauses from et where. Cette jointure nous retourne uniquement les lignes des 2 tables qui satisfont le critÚre précisé dans la condition de jointure.

Les lignes qui ne satisfont pas la condition sont exclues de la table gauche et la table droite de la jointure.

Reprenons notre exemple :

pays

idpaysnomlangue

1

FR

France

français

2

ZA

Afrique du Sud

anglais

3

UK

Grande Bretagne

anglais

equipe

paysequipelangue

FR

Les bleus

français

ZA

Bafana bafana

xhosa

Une requĂȘte de type inner join nous retourner de rĂ©sultats plus cohĂ©rents :

select *
from pays
inner join equipe on pays.pays = equipe.pays;

Le résultat :

idpays.paysnompays.langueequipe.paysequipeequipe.langue

1

FR

France

français

FR

Les bleus

français

2

ZA

Afrique du Sud

anglais

ZA

Bafana bafana

xhosa

Notez que le pays UK est exclu du résultat, parce qu'il n'y a pas de ligne correspondant (selon le critÚre) dans la table equipe.

Vous avez dit de ne jamais utiliser le wildcard *, pourquoi l'utiliser ici ?

C'est juste pour vous indiquer la différence entre natural join et inner join. Vous avez remarqué que natural join nous a retourné une seule colonne pays et une seule colonne langue ? C'est parce que la valeur est identique, donc pourquoi la répéter ?

Avec inner join le critĂšre peut ĂȘtre plus complexe (la clause on peut ĂȘtre un ensemble de conditions avec les opĂ©rateurs and, or et not. L'Ă©quivalence n'est pas garantie). Donc, on est obligĂ© d'afficher toutes les colonnes de la jointure dans le rĂ©sultat.

Certains SGBDR permettent l'utilisation du join (sans le mot inner). Personnellement, je prĂ©fĂšre ĂȘtre explicite dans mes intentions, et je trouve qu'exprimer inner join et plus parlant de ce qui va se passer dans la requĂȘte.

outer join

Il y a souvent la demande de joindre des tables et garder des lignes mĂȘme si une ligne correspondant ne se trouve pas dans l'autre table.

Pour continuer notre exemple, je veux voir la liste entiÚre de pays, malgré le fait qu'ils ont une équipe ou pas. J'utilise left outer join

select *
from pays
left outer join equipe on pays.pays = equipe.pays;

Le résultat est :

idpays.paysnompays.langueequipe.paysequipeequipe.langue

1

FR

France

français

FR

Les bleus

français

2

ZA

Afrique du Sud

anglais

ZA

Bafana bafana

xhosa

3

UK

Grande Bretagne

anglais

null

null

null

Avec le left outer join, on garde toutes les lignes de la table gauche (avant l'opérateur left outer join), malgré l'existence d'une ligne correspondante coté droite. On voit que pour le pays UK, il y a des valeurs null à la place d'une équipe.

D'autres exemples des outer join :

  • Je veux voir la liste d'articles dans mon catalogue qui avec, en option, le nombre de ventes de l'article (il faut afficher l'article mĂȘme s'il n'y a pas eu de vente)

  • Dans la base saas, je veux voir toutes les interventions et Ă©vĂ©nements. Il faut inclure mĂȘme les interventions qui n'ont pas d'Ă©vĂ©nement.

Il y a trois sortes de outer join :

  • left outer join : Conserver les lignes venant de la relation avant (ou sur la gauche) des mots left outer join, mĂȘme s’il n’y a pas de correspondance dans l’autre relation. Attention : les lignes venant de l’autre relation (sur la droite) peuvent toujours ĂȘtre exclus !

  • right outer join : Conserver les lignes venant de la relation aprĂšs (ou sur la droite) des mots right outer join, mĂȘme s’il n’y a pas de correspondance dans l’autre relation. Attention : les lignes venant de la premiĂšre relation (sur la gauche) peuvent toujours ĂȘtre exclus !

  • full outer join : Conserver toutes les lignes des deux relations.

Exercices

Les exercices suivants sont basés sur la base de données SAKILA, une base de données de test fourni par MySQL.

Récupérez la base SAKILA ici.

Question 1

Utilisez l’opĂ©rateur join afin d’afficher le nom, prĂ©nom, adresse et code postal de chaque membre de l’équipe (staff).

Solution

RĂ©sultat : 2 lignes

select 
  stf.first_name, 
  stf.last_name, 
  adr.address
from staff as stf
inner join address adr
on stf.address_id = adr.address_id; 

Question 2

Utilisez join afin de calculer le montant total encaissĂ© par chaque membre de l’équipe pendant le mois d’aoĂ»t 2005.

Solution

RĂ©sultat : 2 lignes

select 
  stf.first_name, 
  stf.last_name, 
  sum(pay.amount)
from 
  staff as stf
inner join payment as pay
  on stf.staff_id = pay.staff_id
where 
  month(pay.payment_date) = 8 and 
  year(pay.payment_date) = 2005
group by 
  stf.first_name, stf.last_name;

Question 3

Listez le nom et prĂ©nom de tous les clients (customer). Si un client partage son nom avec un acteur, affichez le nom, prĂ©nom et ID de l’acteur aussi (sinon, affichez null).

Solution

RĂ©sultat : 620 lignes

select 
  c.first_name, 
  c.last_name, 
  a.actor_id, 
  a.first_name, 
  a.last_name
from customer as c
left outer join actor as a
  on c.last_name = a.last_name;

DerniĂšre mise Ă  jour