Aller au contenu

Partie 3 : Fusion de tables


Imaginons qu'on veuille stocker les résultats des élèves avec les informations de leur classe. Si on met tout dans une seule table, les informations de classe sont répétées pour chaque élève :

NomClasseProfesseur principalSalleNote
Dupont1NSI-AM. Duval20415
Martin1NSI-AM. Duval20412
Durand1NSI-BMme Leroux10818
Petit1NSI-AM. Duval2049

Les informations « M. Duval » et « 204 » sont répétées trois fois. C'est de la redondance.

On sépare en deux tables reliées par un attribut commun (la clé) :

Table eleves :

NomClasseNote
Dupont1NSI-A15
Martin1NSI-A12
Durand1NSI-B18
Petit1NSI-A9

Table classes :

ClasseProfesseur principalSalle
1NSI-AM. Duval204
1NSI-BMme Leroux108

L'attribut Classe est présent dans les deux tables : c'est la clé de jointure. Dans la table eleves, on l'appelle clé étrangère car elle fait référence à une entrée d'une autre table.


La jointure (ou fusion) consiste à combiner les lignes de deux tables qui partagent une même valeur pour un attribut commun.

Pour chaque ligne de la première table, on cherche la ligne correspondante dans la seconde table (celle qui a la même valeur de clé), et on fusionne les deux.

eleves = [
{"nom": "Dupont", "classe": "1NSI-A", "note": 15},
{"nom": "Martin", "classe": "1NSI-A", "note": 12},
{"nom": "Durand", "classe": "1NSI-B", "note": 18},
{"nom": "Petit", "classe": "1NSI-A", "note": 9}
]
classes = [
{"classe": "1NSI-A", "prof": "M. Duval", "salle": 204},
{"classe": "1NSI-B", "prof": "Mme Leroux", "salle": 108}
]
def jointure(table1, table2, cle):
"""Fusionne deux tables sur un attribut commun."""
resultat = []
for ligne1 in table1:
for ligne2 in table2:
if ligne1[cle] == ligne2[cle]:
fusion = {}
fusion.update(ligne1)
fusion.update(ligne2)
resultat.append(fusion)
return resultat
table_complete = jointure(eleves, classes, "classe")
for e in table_complete:
print(e)

Résultat :

{"nom": "Dupont", "classe": "1NSI-A", "note": 15, "prof": "M. Duval", "salle": 204}
{"nom": "Martin", "classe": "1NSI-A", "note": 12, "prof": "M. Duval", "salle": 204}
{"nom": "Durand", "classe": "1NSI-B", "note": 18, "prof": "Mme Leroux", "salle": 108}
{"nom": "Petit", "classe": "1NSI-A", "note": 9, "prof": "M. Duval", "salle": 204}

Pour chaque élève, on parcourt toutes les classes pour trouver celle qui a la même valeur de "classe". Quand on la trouve, on fusionne les deux dictionnaires :

Dupont (1NSI-A) + classe 1NSI-A (M. Duval, 204) → fusion
Martin (1NSI-A) + classe 1NSI-A (M. Duval, 204) → fusion
Durand (1NSI-B) + classe 1NSI-B (Mme Leroux, 108) → fusion
Petit (1NSI-A) + classe 1NSI-A (M. Duval, 204) → fusion

Si un élève a une classe qui n'existe pas dans la table classes, il ne sera pas inclus dans le résultat de la jointure :

eleves = [
{"nom": "Dupont", "classe": "1NSI-A", "note": 15},
{"nom": "Moreau", "classe": "1NSI-C", "note": 14} # classe inconnue
]
# Moreau n'apparait pas dans le résultat car "1NSI-C" n'est pas dans classes

C'est le comportement standard de la jointure : seules les lignes ayant une correspondance dans les deux tables sont conservées.

Si les deux tables ont un attribut de même nom (autre que la clé), la valeur de la seconde table écrase celle de la première (avec update()). Pour l'éviter, on peut renommer les attributs :

def jointure_safe(table1, table2, cle):
"""Jointure avec préfixe pour éviter les conflits."""
resultat = []
for l1 in table1:
for l2 in table2:
if l1[cle] == l2[cle]:
fusion = {}
for k, v in l1.items():
fusion[k] = v
for k, v in l2.items():
if k != cle:
fusion[k] = v
resultat.append(fusion)
return resultat

Une clé primaire est un attribut (ou un ensemble d'attributs) qui identifie de manière unique chaque ligne d'une table. Aucune autre ligne ne peut avoir la même valeur.

Dans la table classes, "classe" est la clé primaire : chaque classe n'apparait qu'une seule fois.

Une clé étrangère est un attribut d'une table qui fait référence à la clé primaire d'une autre table.

Dans la table eleves, "classe" est une clé étrangère : elle fait référence à la clé primaire de la table classes.

Table eleves Table classes
+---------+---------+------+ +---------+------------+-------+
| nom | classe | note | | classe | prof | salle |
+---------+---------+------+ +---------+------------+-------+
| Dupont | 1NSI-A ------>| | 1NSI-A | M. Duval | 204 |
| Martin | 1NSI-A ------>| | 1NSI-B | Mme Leroux | 108 |
| Durand | 1NSI-B ------>| +---------+------------+-------+
| Petit | 1NSI-A ------>|
+---------+---------+------+
  • Pas de redondance : les informations de chaque classe ne sont stockées qu'une seule fois.
  • Cohérence : si le prof principal change, on ne modifie qu'une seule ligne dans classes (et non toutes les lignes de eleves).
  • Organisation : les données sont séparées par thème logique.

On peut enchaîner les jointures pour fusionner plus de deux tables :

eleves = [
{"id_eleve": 1, "nom": "Dupont", "id_classe": 101},
{"id_eleve": 2, "nom": "Martin", "id_classe": 101},
{"id_eleve": 3, "nom": "Durand", "id_classe": 102}
]
classes = [
{"id_classe": 101, "nom_classe": "1NSI-A", "id_prof": 201},
{"id_classe": 102, "nom_classe": "1NSI-B", "id_prof": 202}
]
professeurs = [
{"id_prof": 201, "nom_prof": "M. Duval"},
{"id_prof": 202, "nom_prof": "Mme Leroux"}
]
# Étape 1 : fusionner élèves et classes
etape1 = jointure(eleves, classes, "id_classe")
# Étape 2 : fusionner le résultat avec les professeurs
resultat = jointure(etape1, professeurs, "id_prof")
for e in resultat:
print(f"{e['nom']} - {e['nom_classe']} - {e['nom_prof']}")
# Dupont - 1NSI-A - M. Duval
# Martin - 1NSI-A - M. Duval
# Durand - 1NSI-B - Mme Leroux

ConceptDétail
RedondanceRépétition inutile d'informations dans une table
JointureFusion de deux tables sur un attribut commun
Clé primaireAttribut identifiant de manière unique chaque ligne
Clé étrangèreAttribut faisant référence à la clé primaire d'une autre table
AlgorithmeDouble boucle : pour chaque ligne de T1, chercher la correspondance dans T2
Sans correspondanceLes lignes sans correspondance sont exclues du résultat