13 - Traitement de données en tables

QCM – vérification des prérequis

Pour chaque question, une seule réponse est correcte.

1. Qu’est-ce qu’un fichier CSV ?

  • A. Un fichier image compressé
  • B. Un fichier texte dont les données sont séparées par un caractère délimiteur (virgule, point-virgule…)
  • C. Un fichier binaire utilisé par les bases de données
  • D. Un fichier exécutable Python
Correction

Réponse : B. CSV signifie Comma-Separated Values. C’est un fichier texte dans lequel chaque ligne représente un enregistrement et les champs sont séparés par un délimiteur (souvent ; en France, , en anglais).

  • A est faux : les fichiers image sont en PNG, JPG, etc.
  • C est faux : un fichier CSV est un fichier texte, lisible avec un simple éditeur.
  • D est faux : un fichier Python a l’extension .py.

2. En Python, quel module permet de lire un fichier CSV avec des dictionnaires ?

  • A. json
  • B. csv
  • C. os
  • D. math
Correction

Réponse : B. Le module csv fournit notamment csv.DictReader qui lit chaque ligne comme un dictionnaire dont les clés sont les en-têtes (descripteurs) du fichier.

  • A est faux : json traite les fichiers au format JSON, pas CSV.
  • C est faux : os gère les interactions avec le système d’exploitation (chemins, fichiers).
  • D est faux : math fournit des fonctions mathématiques.

3. Que renvoie csv.DictReader lorsqu’on itère dessus ?

  • A. Une liste de listes
  • B. Un dictionnaire unique contenant toutes les données
  • C. Un itérateur de dictionnaires, un par ligne
  • D. Une chaîne de caractères
Correction

Réponse : C. DictReader renvoie un itérateur : à chaque itération, on obtient un dictionnaire représentant une ligne du fichier, avec les en-têtes comme clés.

  • A est faux : c’est csv.reader (sans « Dict ») qui renvoie des listes.
  • B est faux : on obtient un dictionnaire par ligne, pas un seul pour tout le fichier.
  • D est faux : le résultat n’est pas une simple chaîne.

4. Comment trier une liste de dictionnaires par la valeur d’une clé "note" ?

  • A. liste.sort()
  • B. sorted(liste, key=lambda d: d["note"])
  • C. liste.filter("note")
  • D. sorted(liste)
Correction

Réponse : B. sorted avec le paramètre key permet de spécifier le critère de tri. La fonction lambda d: d["note"] extrait la valeur associée à la clé "note" pour chaque dictionnaire.

  • A est faux : sort() sans argument tente de comparer les dictionnaires entre eux, ce qui lève une erreur.
  • C est faux : filter n’est pas une méthode de liste (et ne trie pas).
  • D est faux : même problème que A, Python ne sait pas comment comparer des dictionnaires par défaut.

Le fichier nat2020.csv est un jeu de données ouvertes concernant les prénoms donnés en France de 1900 à 2020.

TP : exploration du fichier des prénoms

Dans ce TP, vous allez explorer un jeu de données réel issu de l’INSEE : le fichier des prénoms attribués aux enfants nés en France depuis 1900. Vous apprendrez à importer, rechercher, trier et fusionner des données organisées en tables. Durée : 5 à 6 heures.

Compétences à acquérir : importer une table depuis un fichier CSV ; rechercher les lignes d’une table vérifiant des critères exprimés en logique propositionnelle ; recherche de doublons, tests de cohérence ; trier une table suivant une colonne ; construire une nouvelle table en combinant les données de deux tables ; notion de domaine de valeurs.

Les données utilisées proviennent de l’INSEE et sont placées sous licence ouverte.

Présentation des données

Télécharger l’archive prenoms.zip et la décompresser dans votre répertoire de travail : elle contient le fichier prenoms.csv (13 Mo, plus de 700 000 lignes) des prénoms attribués aux enfants nés en France entre 1900 et 2024. Il est composé de quatre colonnes (descripteurs) :

  • sexe : 1 pour masculin, 2 pour féminin ;
  • prenom : le prénom (en majuscules, sans accents) ;
  • periode : l’année de naissance ;
  • valeur : le nombre de naissances (arrondi au multiple de 5).

Extrait du fichier :

sexe;prenom;periode;valeur
1;AARON;1980;5
1;AARON;1990;25
1;AARON;2000;120
1;AARON;2010;1605
2;EMMA;2000;6845
2;EMMA;2010;6400

Import des données

Exercice 1 : chargement du fichier CSV.

  1. Compléter la fonction charger_csv ci-dessous. Cette fonction prend en paramètre le nom d’un fichier CSV et renvoie une liste de dictionnaires.

    Remarque. La fonction csv.DictReader lit toutes les valeurs comme des chaînes de caractères. Par exemple, ligne['valeur'] vaut '295' (une chaîne) et non 295 (un entier). Il faut donc convertir les valeurs numériques en entiers pour pouvoir faire des calculs.

    import csv
    
    def charger_csv(nom_fichier):
        """
        Charge un fichier CSV et renvoie une liste de dictionnaires.
        Chaque dictionnaire représente une ligne du fichier.
        """
        table = []
        with open(nom_fichier, 'r', encoding='utf-8') as fichier:
            lecteur = csv.DictReader(fichier, delimiter=';')
            for ligne in lecteur:
                # À COMPLÉTER : convertir les chaînes en entiers
                ligne['valeur'] = ...
                ligne['periode'] = ...
                table.append(ligne)
        return table
    
    # Chargement des données
    prenoms = charger_csv('prenoms.csv')
    
  2. Écrire l’instruction qui affiche le nombre total de lignes dans la table.

  3. Écrire une boucle qui affiche les 5 premières lignes de la table. Que représente chaque ligne ?

  4. Écrire l’instruction qui affiche les descripteurs (les clés) de la première ligne.

Correction
  1. Code complété :
import csv

def charger_csv(nom_fichier):
    table = []
    with open(nom_fichier, 'r', encoding='utf-8') as fichier:
        lecteur = csv.DictReader(fichier, delimiter=';')
        for ligne in lecteur:
            ligne['valeur'] = int(ligne['valeur'])
            ligne['periode'] = int(ligne['periode'])
            table.append(ligne)
    return table

prenoms = charger_csv('prenoms.csv')
  1. Nombre de lignes : print(len(prenoms)).

  2. Affichage des 5 premières lignes :

for i in range(5):
    print(prenoms[i])
Chaque ligne est un p-uplet nommé (dictionnaire) qui associe un prénom, un sexe et une année au nombre de naissances correspondant.
  1. Descripteurs : print(list(prenoms[0].keys())).

Exercice 2 : tests de cohérence.

Avant d’exploiter des données, il est important de vérifier leur cohérence.

  1. Écrire une fonction verifier_sexe(table) qui prend en paramètre une table et renvoie True si toutes les valeurs du descripteur sexe sont '1' ou '2', et False sinon.
  2. Écrire une fonction annees_presentes(table) qui prend en paramètre une table et renvoie la liste des années présentes (sans doublons).
  3. En utilisant cette fonction, vérifier s’il y a des années manquantes entre 1900 et 2024.
Correction
  1. Vérification du sexe :
def verifier_sexe(table):
    for ligne in table:
        if ligne['sexe'] != '1' and ligne['sexe'] != '2':
            return False
    return True

print(verifier_sexe(prenoms))
  1. Années présentes :
def annees_presentes(table):
    annees = []
    for ligne in table:
        if ligne['periode'] not in annees:
            annees.append(ligne['periode'])
    return sorted(annees)  # tri optionnel

annees = annees_presentes(prenoms)
print(min(annees), max(annees))
  1. Vérification des années manquantes :
manquantes = []
for a in range(1900, 2025):
    if a not in annees:
        manquantes.append(a)
print(manquantes)

Recherche dans une table

Exercice 3 : recherche simple.

  1. Écrire une fonction rechercher_prenom(table, prenom) qui prend en paramètres une table et un prénom (chaîne de caractères), et renvoie la liste de toutes les lignes dont le descripteur prenom est égal au prénom cherché.
  2. Utiliser cette fonction pour récupérer toutes les lignes correspondant au prénom 'CLAUDE'. Afficher, pour chaque ligne, l’année, le nombre de naissances et le sexe.
  3. Écrire une fonction total_naissances(table, prenom) qui prend en paramètres une table et un prénom, et renvoie le nombre total de naissances pour ce prénom (somme des valeurs du descripteur valeur).
  4. Calculer le total des naissances pour le prénom 'MARIE' puis pour le prénom 'JEAN'.
  5. Question personnelle : rechercher votre propre prénom (en majuscules) et calculer le nombre total de naissances. Que pouvez-vous dire sur la popularité de votre prénom ?
Correction
  1. Fonction de recherche :
def rechercher_prenom(table, prenom):
    resultats = []
    for ligne in table:
        if ligne['prenom'] == prenom:
            resultats.append(ligne)
    return resultats
  1. Évolution de CLAUDE :
claude = rechercher_prenom(prenoms, 'CLAUDE')
for ligne in claude:
    print(ligne['periode'], ligne['valeur'], ligne['sexe'])
On observe que CLAUDE était très populaire dans les années 1930-1950, puis a fortement décliné.
  1. Total des naissances :
def total_naissances(table, prenom):
    total = 0
    for ligne in table:
        if ligne['prenom'] == prenom:
            total = total + ligne['valeur']
    return total
  1. Totaux : print(total_naissances(prenoms, 'MARIE')) et print(total_naissances(prenoms, 'JEAN')).

  2. Réponse personnelle.

Exercice 4 : recherche avec plusieurs critères.

On souhaite rechercher des lignes vérifiant plusieurs critères à la fois.

  1. Écrire une fonction rechercher(table, sexe, annee) qui prend en paramètres une table, un sexe ('1' ou '2') et une année (entier), et renvoie la liste des lignes vérifiant ces deux critères simultanément.
  2. Utiliser cette fonction pour trouver tous les prénoms féminins attribués en 2020. Combien y en a-t-il ?
  3. Combien de prénoms masculins différents ont été attribués en 1950 ? Indication : construire d’abord la liste des lignes correspondantes, puis extraire les prénoms sans doublons.
Correction
  1. Fonction de recherche multicritères :
def rechercher(table, sexe, annee):
    resultats = []
    for ligne in table:
        if ligne['sexe'] == sexe and ligne['periode'] == annee:
            resultats.append(ligne)
    return resultats
  1. Prénoms féminins en 2020 :
feminins_2020 = rechercher(prenoms, '2', 2020)
print(len(feminins_2020))
  1. Prénoms masculins différents en 1950 :
masculins_1950 = rechercher(prenoms, '1', 1950)
prenoms_uniques = []
for ligne in masculins_1950:
    if ligne['prenom'] not in prenoms_uniques:
        prenoms_uniques.append(ligne['prenom'])
print(len(prenoms_uniques))

Exercice 5 : recherche de doublons.

Certains prénoms sont attribués à la fois à des garçons et à des filles. On appelle ces prénoms des « prénoms mixtes ».

  1. Écrire une fonction liste_prenoms_par_sexe(table, sexe) qui prend en paramètres une table et un sexe, et renvoie la liste des prénoms (sans doublons) correspondant à ce sexe.
  2. En utilisant cette fonction, construire la liste masculins des prénoms masculins et la liste feminins des prénoms féminins.
  3. Écrire une fonction prenoms_mixtes(masculins, feminins) qui prend en paramètres ces deux listes et renvoie la liste des prénoms présents dans les deux listes.
  4. Combien y a-t-il de prénoms mixtes ? Donner quelques exemples.
Correction
  1. Liste des prénoms par sexe :
def liste_prenoms_par_sexe(table, sexe):
    prenoms_liste = []
    for ligne in table:
        if ligne['sexe'] == sexe:
            if ligne['prenom'] not in prenoms_liste:
                prenoms_liste.append(ligne['prenom'])
    return prenoms_liste
  1. Construction des listes :
masculins = liste_prenoms_par_sexe(prenoms, '1')
feminins = liste_prenoms_par_sexe(prenoms, '2')
  1. Prénoms mixtes :
def prenoms_mixtes(masculins, feminins):
    mixtes = []
    for prenom in masculins:
        if prenom in feminins:
            mixtes.append(prenom)
    return mixtes
  1. Résultat :
mixtes = prenoms_mixtes(masculins, feminins)
print(len(mixtes))
print(mixtes[:10])

Tri d’une table

Rappel sur les fonctions anonymes (lambda). La fonction sorted(liste, key=fonction) permet de trier une liste. Le paramètre key indique la fonction à utiliser pour extraire la valeur de tri de chaque élément. On utilise souvent une fonction anonyme (ou lambda) pour définir cette fonction de tri. Par exemple, lambda x: x['periode'] est équivalent à :

def extraire_annee(x):
    return x['periode']

L’avantage de la syntaxe lambda est sa concision : on peut définir la fonction directement dans l’appel à sorted, sans avoir à lui donner un nom.

Exercice 6 : tri suivant une colonne.

  1. En utilisant rechercher_prenom, récupérer toutes les lignes correspondant au prénom 'EMMA'.
  2. Trier cette liste par année croissante en utilisant emma_trie = sorted(emma, key=lambda x: x['periode']).
  3. Afficher l’évolution du prénom EMMA entre 2000 et 2024 (année et nombre de naissances).
  4. Écrire une fonction top_prenoms(table, annee, sexe, n) qui prend en paramètres une table, une année, un sexe et un entier n, et renvoie les n prénoms les plus attribués cette année-là pour ce sexe, triés par nombre de naissances décroissant. Indication : utiliser sorted(..., key=lambda x: x['valeur'], reverse=True).
  5. Afficher le top 10 des prénoms féminins en 2024.
Correction
  1. Récupération : emma = rechercher_prenom(prenoms, 'EMMA').

  2. Tri par année : emma_trie = sorted(emma, key=lambda x: x['periode']).

  3. Évolution d’EMMA entre 2000 et 2024 :

for ligne in emma_trie:
    if 2000 <= ligne['periode'] <= 2024:
        print(f"Année {ligne['periode']} : {ligne['valeur']} naissances")
On observe une forte croissance jusqu'en 2005-2006, puis une diminution progressive.
  1. Fonction top_prenoms :
def top_prenoms(table, annee, sexe, n):
    selection = rechercher(table, sexe, annee)
    selection_triee = sorted(selection, key=lambda x: x['valeur'], reverse=True)
    return selection_triee[:n]
  1. Top 10 féminin 2024 :
top_f = top_prenoms(prenoms, 2024, '2', 10)
for i in range(len(top_f)):
    ligne = top_f[i]
    print(f"{i+1}. {ligne['prenom']} ({ligne['valeur']})")

Exercice 7 : analyse de tendances.

  1. Écrire une fonction annee_pic(table, prenom) qui prend en paramètres une table et un prénom, et renvoie l’année où ce prénom a été le plus attribué (c’est-à-dire l’année avec le plus grand nombre de naissances). Indication : utiliser la fonction max(liste, key=...) pour trouver l’élément ayant la plus grande valeur pour un critère donné.
  2. Calculer l’année du pic pour les prénoms suivants : 'BRIGITTE', 'SYLVIE', 'NATHALIE', 'STEPHANIE', 'LEA'. Que remarque-t-on sur la succession de ces prénoms dans le temps ?
  3. Question de réflexion : à votre avis, quels facteurs peuvent influencer les cycles de popularité des prénoms ? Pensez aux médias, aux personnalités célèbres, aux événements historiques…
Correction
  1. Année du pic :
def annee_pic(table, prenom):
    donnees = rechercher_prenom(table, prenom)
    if len(donnees) == 0:
        return None
    ligne_max = max(donnees, key=lambda x: x['valeur'])
    return ligne_max['periode']
  1. Pics des prénoms :
for p in ['BRIGITTE', 'SYLVIE', 'NATHALIE', 'STEPHANIE', 'LEA']:
    print(p, annee_pic(prenoms, p))
On observe une succession de prénoms « à la mode », chacun dominant une décennie. Cela illustre les cycles de popularité des prénoms.
  1. Plusieurs facteurs influencent la popularité des prénoms : les célébrités (actrices, chanteuses, sportives), les personnages de fiction (films, séries, livres), les événements historiques, l’effet de mode et d’imitation sociale, ou encore la volonté de se distinguer quand un prénom devient trop courant.

Fusion de tables

On dispose d’un second fichier origines.csv (à déposer dans votre répertoire de travail) contenant l’origine étymologique de certains prénoms :

prenom;origine;signification
EMMA;germanique;universel ou puissant
GABRIEL;hebreu;force de Dieu
LEA;hebreu;fatiguee ou lionne
LUCAS;grec;lumiere
JADE;espagnol;pierre verte
LOUIS;germanique;illustre au combat

Exercice 8 : chargement et fusion de deux tables.

  1. Charger le fichier origines.csv dans une table origines (sans conversion de types, car toutes les valeurs sont des chaînes) :

    def charger_csv_simple(nom_fichier):
        table = []
        with open(nom_fichier, 'r', encoding='utf-8') as fichier:
            lecteur = csv.DictReader(fichier, delimiter=';')
            for ligne in lecteur:
                table.append(ligne)
        return table
    
    origines = charger_csv_simple('origines.csv')
    
  2. Écrire une fonction fusionner(table1, table2, cle) qui prend en paramètres deux tables et le nom d’un descripteur commun (la clé), et renvoie une nouvelle table contenant les lignes de table1 enrichies des informations de table2 lorsque les valeurs de la clé correspondent.

    Principe : pour chaque ligne de table1, on cherche dans table2 une ligne ayant la même valeur pour le descripteur cle. Si on en trouve une, on crée une nouvelle ligne combinant les informations des deux.

    def fusionner(table1, table2, cle):
        resultat = []
        for ligne1 in table1:
            # À compléter : parcourir table2
            # À compléter : vérifier si la clé correspond
            # À compléter : créer la nouvelle ligne enrichie
        return resultat
    
  3. Fusionner les tables prenoms et origines sur la clé 'prenom'.

  4. Pour chaque prénom présent dans origines, afficher son origine, sa signification et le nombre de naissances en 2024.

Correction
  1. Chargement : voir le code de l’énoncé.

  2. Fonction de fusion (version simple avec deux boucles) :

def fusionner(table1, table2, cle):
    resultat = []
    for ligne1 in table1:
        for ligne2 in table2:
            if ligne1[cle] == ligne2[cle]:
                nouvelle_ligne = {{}}
                for k in ligne1:
                    nouvelle_ligne[k] = ligne1[k]
                for k in ligne2:
                    nouvelle_ligne[k] = ligne2[k]
                resultat.append(nouvelle_ligne)
    return resultat
  1. Fusion :
prenoms_enrichis = fusionner(prenoms, origines, 'prenom')
print(len(prenoms_enrichis))
  1. Affichage enrichi :
for ligne_origine in origines:
    nom = ligne_origine['prenom']
    for ligne in prenoms_enrichis:
        if ligne['prenom'] == nom and ligne['periode'] == 2024:
            print(f"{{nom}} ({{ligne_origine['origine']}}) : {{ligne['valeur']}} naissances")

Pour aller plus loin : optimisation de la fusion. La fonction fusionner ci-dessus utilise deux boucles imbriquées. Pour chaque ligne de table1, elle parcourt toute la table2. Si les deux tables contiennent \(n\) et \(m\) lignes, cela représente \(n \times m\) comparaisons. On peut améliorer l’efficacité en construisant d’abord un dictionnaire d’accès rapide pour table2 :

def fusionner_optimise(table1, table2, cle):
    dict_table2 = {{}}
    for ligne in table2:
        dict_table2[ligne[cle]] = ligne

    resultat = []
    for ligne1 in table1:
        valeur_cle = ligne1[cle]
        if valeur_cle in dict_table2:
            nouvelle_ligne = {{}}
            for k in ligne1:
                nouvelle_ligne[k] = ligne1[k]
            ligne2 = dict_table2[valeur_cle]
            for k in ligne2:
                nouvelle_ligne[k] = ligne2[k]
            resultat.append(nouvelle_ligne)
    return resultat

Cette version ne parcourt table2 qu’une seule fois pour construire le dictionnaire, puis l’accès à chaque élément se fait en temps constant.

Exercice 9 : notion de domaine de valeurs.

  1. Écrire une fonction valeurs_distinctes(table, descripteur) qui prend en paramètres une table et le nom d’un descripteur, et renvoie la liste des valeurs distinctes (sans doublons) de ce descripteur dans la table.
  2. Appliquer cette fonction au descripteur 'origine' de la table origines. La liste obtenue s’appelle le domaine de valeurs du descripteur.
  3. Quel est le domaine de valeurs du descripteur 'sexe' dans la table prenoms ?
Correction
  1. Fonction :
def valeurs_distinctes(table, descripteur):
    valeurs = []
    for ligne in table:
        if ligne[descripteur] not in valeurs:
            valeurs.append(ligne[descripteur])
    return valeurs
  1. Domaine de 'origine' : print(valeurs_distinctes(origines, 'origine')).

  2. Domaine de 'sexe' : print(valeurs_distinctes(prenoms, 'sexe')), soit ['1', '2'].

Le domaine de valeurs d’un descripteur est l’ensemble des valeurs que ce descripteur peut prendre. Cette notion est fondamentale en bases de données pour garantir la cohérence des données.

Pour aller plus loin

Exercice 10 : synthèse, portrait d’un prénom.

Écrire une fonction portrait_prenom(table, prenom) qui prend en paramètres une table et un prénom, et affiche un résumé complet de ce prénom :

  • le nombre total de naissances ;
  • l’année du pic de popularité et le nombre de naissances cette année-là ;
  • la répartition entre masculin et féminin (en pourcentage) ;
  • la première et la dernière année d’apparition dans les données.

Tester la fonction avec les prénoms 'CAMILLE', 'DOMINIQUE' et 'CLAUDE'.

Correction
def portrait_prenom(table, prenom):
    donnees = rechercher_prenom(table, prenom)

    if len(donnees) == 0:
        print(f"Aucune donnée pour le prénom {prenom}")
        return

    # Total des naissances
    total = 0
    for ligne in donnees:
        total = total + ligne['valeur']

    # Année du pic
    ligne_max = max(donnees, key=lambda x: x['valeur'])
    annee_max = ligne_max['periode']
    nombre_max = ligne_max['valeur']

    # Répartition M/F
    total_m = 0
    total_f = 0
    for ligne in donnees:
        if ligne['sexe'] == '1':
            total_m = total_m + ligne['valeur']
        else:
            total_f = total_f + ligne['valeur']

    if total > 0:
        pct_m = round(100 * total_m / total, 1)
        pct_f = round(100 * total_f / total, 1)
    else:
        pct_m = 0
        pct_f = 0

    # Première et dernière année
    annees = []
    for ligne in donnees:
        annees.append(ligne['periode'])
    premiere = min(annees)
    derniere = max(annees)

    # Affichage
    print(f"=== Portrait du prénom {prenom} ===")
    print(f"Total des naissances : {total}")
    print(f"Pic de popularité : {annee_max} ({nombre_max} naissances)")
    print(f"Répartition : {pct_m}% masculin, {pct_f}% féminin")
    print(f"Période : de {premiere} à {derniere}")

# Tests
portrait_prenom(prenoms, 'CAMILLE')
portrait_prenom(prenoms, 'DOMINIQUE')
portrait_prenom(prenoms, 'CLAUDE')

Autres jeux de données

Exercice 11 : requêtes sur les villes de France.

Le fichier villes.csv décrit les communes françaises (nom, département, population en 2012, densité, altitude minimale, etc.). Écrire les programmes Python qui répondent aux questions suivantes.

  1. Donner la liste des villes du Rhône qui ont une altitude minimale supérieure à 500 m.
  2. Donner la liste des villes du Rhône qui ont une densité d’habitants inférieure à 200 hab/km² (la table contiendra trois colonnes : le nom de la ville, la densité de la population et l’altitude minimale).
  3. Donner la liste de toutes les villes françaises ayant une densité de population supérieure à 50 hab/km² et une altitude minimale supérieure à 1500 m.
  4. Donner le nombre d’habitants du Rhône en 2012.
  5. Trier les données de façon à obtenir la ville française ayant l’altitude minimale la plus élevée.
  6. Quelle est la ville française ayant la densité de population la plus forte ?

Exercice 12 : concaténation et jointure.

  1. Les communes du département des Alpes-Maritimes ont été oubliées dans le fichier villes.csv et se trouvent dans le fichier villes-06.csv . Les descripteurs étant les mêmes dans les deux fichiers, écrire un programme Python permettant de fusionner les deux tables.

  2. On dispose de deux fichiers clients.csv et commandes.csv qui n’ont qu’un descripteur en commun : n_client.

    1. Écrire un programme Python qui permet de fusionner les deux tables.
    2. L’ordre a-t-il une importance lors de la fusion ?
    3. Si vous avez fusionné à partir de la table clients, vous avez peut-être remarqué que Mme Élodie Gaulin (numéro de client 895) est bien présente mais qu’il n’y a pas de commande correspondante. Pourquoi d’après vous ?
    4. De la même manière, aucune trace de la commande numéro 1324 du 01/02/2017 dans la table fusionnée. Pourquoi d’après vous ?

    On remarquera aussi que Mme Neufville a fait deux commandes. D’où la nécessité de structurer correctement les données.