Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

Chapitre 1014 minIntermédiaire

Normalisation : 1FN, 2FN, 3FN

Par Josselyn C., prof de BTS SIO. Mis à jour le .

À la fin de ce chapitre, tu sauras

  • repérer les anomalies d'une table mal conçue ;
  • dire si une table est en 1FN, 2FN ou 3FN ;
  • décomposer une table jusqu'à la 3FN sans perte ;
  • savoir quand une dénormalisation se justifie.

4 exercices corrigés1 schéma

La normalisation est une méthode qui vérifie qu'une table ne contient pas de redondance, à l'aide de règles appelées formes normales. La première forme normale (1FN) exige des valeurs atomiques, la deuxième (2FN) que chaque attribut dépende de toute la clé, la troisième (3FN) qu'il ne dépende que de la clé. Une base en 3FN évite les anomalies de mise à jour, d'insertion et de suppression. Ce chapitre définit chaque forme normale et normalise pas à pas un bon de commande.

Les anomalies d'une table mal conçue

Une petite société de maintenance informatique vend aussi du matériel à ses clients. Son ancien logiciel enregistre les commandes dans une seule table :

num_cdedate_cdenum_clientnom_clientville_clientref_produitlibelleprix_unitairequantite
C1012026-11-0312Garage MorelDoleSSD1TSSD 1 To79,902
C1012026-11-0312Garage MorelDoleECR24Écran 24 pouces149,001
C1022026-11-047Cabinet RouxBesançonSSD1TSSD 1 To79,905
C1032026-11-0412Garage MorelDoleCLAVClavier sans fil34,503

Le nom et la ville du Garage Morel sont recopiés trois fois, le libellé et le prix du SSD deux fois. Cette redondance provoque trois anomalies :

  • Anomalie de mise à jour. Le Garage Morel déménage à Lons-le-Saunier : il faut modifier trois lignes. Une oubliée, et le client a deux villes.
  • Anomalie d'insertion. Un nouveau produit arrive au catalogue. Tant que personne ne l'a commandé, on ne peut pas l'enregistrer : il n'y a pas de numéro de commande à mettre dans la ligne.
  • Anomalie de suppression. On supprime la commande C102, annulée. Le Cabinet Roux disparaît avec elle : c'était sa seule ligne.

Les formes normales donnent une méthode pour découper cette table en plusieurs, sans rien perdre, jusqu'à faire disparaître ces anomalies. Elles reposent sur les dépendances fonctionnelles du chapitre 3.

Première forme normale (1FN)

Une table est en première forme normale si :

  1. elle a une clé primaire ;
  2. chaque colonne contient une valeur atomique : une seule valeur, pas une liste ;
  3. elle ne contient pas de groupe répétitif : pas de colonnes tel1, tel2, tel3, ni produit1, produit2.

Le premier logiciel de la société, encore plus ancien, avait une ligne par commande :

num_cdedate_cdenum_clientnom_clientville_clientproduits
C1012026-11-0312Garage MorelDoleSSD1T SSD 1 To 79,90 x2 ; ECR24 Écran 24 pouces 149,00 x1

La colonne produits contient une liste : impossible de compter les SSD vendus, de trier par produit, d'interdire une faute de frappe. La table n'est pas en 1FN. Pour l'y mettre, on écrit une ligne par produit commandé, ce qui donne la grande table du paragraphe précédent. Sa clé primaire est le couple (num_cde, ref_produit) : une commande contient plusieurs produits, un produit figure dans plusieurs commandes, mais un produit n'apparaît qu'une fois par commande.

Deuxième forme normale (2FN)

Une table est en deuxième forme normale si elle est en 1FN et si chaque colonne qui ne fait pas partie de la clé dépend de toute la clé, et non d'une partie seulement. Autrement dit, toutes les DF entre la clé et les autres colonnes sont élémentaires.

La question ne se pose que pour une clé composée : une table en 1FN dont la clé est une seule colonne est automatiquement en 2FN.

Dans la table en 1FN, la clé est (num_cde, ref_produit). Écrivons les DF :

text
num_cde               → date_cde, num_client
num_client            → nom_client, ville_client
ref_produit           → libelle, prix_unitaire
(num_cde, ref_produit) → quantite

Seule quantite dépend de toute la clé. date_cde, num_client, nom_client et ville_client dépendent de num_cde seul ; libelle et prix_unitaire de ref_produit seul. La table n'est pas en 2FN.

On la met en 2FN en sortant chaque groupe d'attributs qui dépend d'une partie de la clé dans une nouvelle table, dont la clé est cette partie :

commande (num_cde, date_cde, num_client, nom_client, ville_client)

produit (ref_produit, libelle, prix_unitaire)

ligne_commande (#num_cde, #ref_produit, quantite)

L'anomalie d'insertion d'un produit a disparu : un produit s'enregistre dans produit, sans commande.

Troisième forme normale (3FN)

Une table est en troisième forme normale si elle est en 2FN et si aucune colonne qui ne fait pas partie de la clé ne dépend d'une autre colonne non clé. Autrement dit, il n'y a pas de DF transitive entre la clé et les autres colonnes.

La table commande est en 2FN (sa clé est simple), mais :

text
num_cde → num_client → nom_client, ville_client

nom_client et ville_client dépendent de num_client, qui n'est pas la clé : c'est une DF transitive. Le Garage Morel est encore recopié sur chacune de ses commandes. La table n'est pas en 3FN.

On la met en 3FN en sortant les colonnes qui dépendent d'une colonne non clé dans une nouvelle table, dont la clé est cette colonne ; la colonne reste dans la table d'origine, comme clé étrangère :

client (num_client, nom_client, ville_client)

commande (num_cde, date_cde, #num_client)

Les anomalies de mise à jour et de suppression ont disparu : la ville du Garage Morel est écrite une fois, et supprimer la commande C102 ne supprime pas le Cabinet Roux.

Le résultat

Table brute : une liste dans une colonnecommandenum_cde, date_cde, num_client, nom_client, ville_client,produits = « SSD1T SSD 1 To 79,90 x2 ; ECR24 … »une ligne par produit commandé1FN : clé (num_cde, ref_produit)commandenum_cde, ref_produit, date_cde, num_client, nom_client,ville_client, libelle, prix_unitaire, quantitesortir les dépendances partielles2FNcommandenum_cdedate_cdenum_clientnom_clientville_clientproduitref_produitlibelleprix_unitaireligne_commande#num_cde#ref_produitquantitesortir la dépendance transitive3FNclientnum_clientnom_clientville_clientcommandenum_cdedate_cde#num_clientproduitref_produitlibelleprix_unitaireligne_commande#num_cde#ref_produitquantite
De la table brute à la 3FN : une étape par forme normale

Le schéma final, en 3FN :

client (num_client, nom_client, ville_client)

commande (num_cde, date_cde, #num_client)

produit (ref_produit, libelle, prix_unitaire)

ligne_commande (#num_cde, #ref_produit, quantite)

C'est exactement le MLD qu'on aurait obtenu en partant d'un MCD avec trois entités (CLIENT, COMMANDE, PRODUIT), une association un à plusieurs (PASSER) et une association plusieurs à plusieurs porteuse (CONTENIR, avec la quantité). Ce n'est pas un hasard.

Décomposer sans perte

Découper une table n'a d'intérêt que si l'on peut retrouver toutes les informations d'origine par des jointures, sans en perdre ni en inventer. La règle qui le garantit : la source de la DF devient la clé de la nouvelle table et reste dans l'ancienne, où elle devient clé étrangère. C'est ce qu'on a fait avec num_client, gardé dans commande.

La jointure des quatre tables redonne la table d'origine, ligne pour ligne :

sql
SELECT c.num_cde, c.date_cde, cl.num_client, cl.nom_client, cl.ville_client,
       p.ref_produit, p.libelle, p.prix_unitaire, l.quantite
FROM ligne_commande l
JOIN commande c  ON c.num_cde = l.num_cde
JOIN client cl   ON cl.num_client = c.num_client
JOIN produit p   ON p.ref_produit = l.ref_produit;

Si on avait découpé n'importe comment, par exemple en mettant nom_client dans une table sans num_client, on n'aurait plus su à quel client appartient quel nom.

Normalisation et MCD

Un MCD bien construit (chaque propriété dépend de l'identifiant de son entité, et de lui seul ; les propriétés qui dépendent de plusieurs identifiants sont sur des associations) donne directement des tables en 3FN. La normalisation sert alors à vérifier le travail.

Elle sert aussi dans deux situations où l'on n'a pas de MCD :

  • pour reprendre des données existantes : un tableur, un export, une vieille base, qu'on normalise pour concevoir la nouvelle ;
  • pour juger une table dans un sujet ou dans le code d'un collègue : un simple coup d'œil aux DF dit si elle contient de la redondance.

Au-delà de la 3FN : la forme normale de Boyce-Codd

La forme normale de Boyce-Codd (FNBC, ou BCNF en anglais) est un peu plus stricte que la 3FN : toute source d'une DF doit être une clé candidate. Elle ne diffère de la 3FN que dans des cas rares, où une colonne non clé détermine une partie de la clé.

Exemple : dans un lycée, chaque professeur n'enseigne qu'une matière, et un élève a un seul professeur par matière. La table suivre (num_eleve, code_matiere, num_professeur) est en 3FN, mais num_professeur → code_matiere, et num_professeur n'est pas une clé : la matière d'un professeur est recopiée pour chacun de ses élèves. En FNBC, on découpe en enseigner (num_professeur, code_matiere) et suivre (#num_eleve, #num_professeur). Le BTS ne demande pas la FNBC ; il existe aussi des formes normales supérieures (4FN, 5FN), réservées aux cas d'école.

Dénormaliser volontairement

Une base normalisée oblige à joindre des tables pour reconstituer l'information. Dans de rares cas, on accepte une redondance contrôlée pour gagner en performance : un compteur du nombre de commandes stocké dans client, une table de statistiques recalculée chaque nuit, un entrepôt de données pour l'informatique décisionnelle.

La dénormalisation a un prix : il faut maintenir la copie à jour (par un trigger, un traitement planifié ou le code), sinon les anomalies reviennent. Elle se décide après avoir normalisé, se justifie par une mesure (une requête trop lente, prouvée avec EXPLAIN : chapitre Index du cours SQL) et se documente. Pour un projet de BTS, la bonne réponse est presque toujours : rester en 3FN, et utiliser une vue pour simplifier les requêtes.

Exercices

Exercice 1 sur 4 Quelle forme normale ? Réussi À revoir

Pour chaque table, donne la plus haute forme normale atteinte (parmi 1FN, 2FN, 3FN) et justifie par une DF. Les clés primaires sont en gras.

  1. inscription (num_etudiant, code_module, date_inscription, nom_etudiant)
  2. employe (matricule, nom, code_service, libelle_service)
  3. client (num_client, nom, telephones), où telephones contient « 06 12 34 56 78, 03 81 00 00 00 »
  4. produit (ref_produit, libelle, prix_unitaire, code_tva), avec code_tva → taux_tva rangé dans une autre table
  5. note (num_etudiant, code_matiere, note, coef_matiere)
Voir la réponseMasquer la réponse de l'exercice 1
  1. 1FN seulement : num_etudiant → nom_etudiant, une partie de la clé. On sort etudiant (num_etudiant, nom_etudiant).
  2. 2FN (clé simple), mais pas 3FN : code_service → libelle_service, une DF transitive. On sort service (code_service, libelle_service).
  3. Pas en 1FN : telephones contient une liste. On crée telephone (#num_client, numero), ou deux colonnes tel_fixe et tel_mobile si l'on n'en a jamais plus de deux, chacune avec un sens précis.
  4. 3FN : chaque colonne dépend de ref_produit, et le taux est déjà dans sa table.
  5. 1FN seulement : code_matiere → coef_matiere, une partie de la clé (le coefficient est celui de la matière, pas celui de la note). On sort matiere (code_matiere, coef_matiere).

Tu avais trouvé ?

Exercice 2 sur 4 Normaliser un suivi de tickets Réussi À revoir

Un service informatique suit ses tickets dans un tableur, une ligne par technicien intervenu sur un ticket :

num_ticket, date_ouverture, matricule_demandeur, nom_demandeur, code_service, libelle_service, code_tech, nom_tech, duree_min

Règles : un ticket est ouvert par un seul demandeur ; un demandeur appartient à un seul service ; plusieurs techniciens peuvent intervenir sur un ticket, et l'on note la durée totale passée par chacun.

  1. Quelle est la clé primaire ? Écris les DF.
  2. Mets la table en 2FN, puis en 3FN.
Voir la réponseMasquer la réponse de l'exercice 2
  1. Une ligne correspond à un couple (ticket, technicien) : la clé est (num_ticket, code_tech). Les DF :

    text
    num_ticket          → date_ouverture, matricule_demandeur
    matricule_demandeur → nom_demandeur, code_service
    code_service        → libelle_service
    code_tech           → nom_tech
    (num_ticket, code_tech) → duree_min
    
  2. 2FN : on sort ce qui dépend de num_ticket seul et de code_tech seul.

    • ticket (num_ticket, date_ouverture, matricule_demandeur, nom_demandeur, code_service, libelle_service)
    • technicien (code_tech, nom_tech)
    • intervenir (#num_ticket, #code_tech, duree_min)

    3FN : ticket contient deux DF transitives (num_ticket → matricule_demandeur → nom_demandeur, code_service, puis code_service → libelle_service). On les sort l'une après l'autre.

    • service (code_service, libelle_service)
    • demandeur (matricule_demandeur, nom_demandeur, #code_service)
    • ticket (num_ticket, date_ouverture, #matricule_demandeur)
    • technicien (code_tech, nom_tech)
    • intervenir (#num_ticket, #code_tech, duree_min)

    Cinq tables en 3FN. Compare avec la base parc du cours SQL, qui organise les mêmes informations avec une table intervention munie de son propre identifiant, pour pouvoir enregistrer plusieurs passages du même technicien.

Tu avais trouvé ?

Exercice 3 sur 4 Stocker un compteur ? Réussi À revoir

Le tableau de bord du service informatique affiche, pour chaque service, le nombre de tickets ouverts. Un développeur propose d'ajouter une colonne nb_tickets_ouverts dans la table service. Qu'en penses-tu ?

Voir la réponseMasquer la réponse de l'exercice 3

C'est une donnée calculée (on compte les tickets ouverts des demandeurs du service) : la stocker crée une redondance, puisque l'information existe déjà sous forme de lignes dans la table des tickets, et un risque d'incohérence (un ticket clos sans mise à jour du compteur). La bonne solution est une requête avec COUNT et GROUP BY, éventuellement enregistrée dans une vue. On ne stockerait le compteur que si la requête était mesurée trop lente, et il faudrait alors le maintenir par des triggers sur la table des tickets.

Tu avais trouvé ?

Exercice 4 sur 4 Vrai ou faux Réussi À revoir

  1. Une table dont la clé primaire est une seule colonne est toujours en 2FN, si elle est en 1FN.
  2. Une table en 3FN ne contient aucune redondance.
  3. Normaliser, c'est fusionner les tables pour éviter les jointures.
  4. Une table qui contient les colonnes jour1, jour2, …, jour7 n'est pas en 1FN.
  5. Garder le prix payé dans la ligne de commande viole la 2FN.
Voir la réponseMasquer la réponse de l'exercice 4
  1. Vrai : il n'y a pas de « partie de la clé » dont une colonne pourrait dépendre.
  2. Faux : la 3FN élimine l'essentiel des redondances, mais pas toutes. La FNBC en retire d'autres, dues à des DF dont la source n'est pas une clé (exemple du professeur et de sa matière), et les formes supérieures en retirent encore d'autres. Pour une base de gestion, la 3FN suffit presque toujours.
  3. Faux : c'est l'inverse, on découpe les tables. Fusionner pour éviter des jointures s'appelle dénormaliser.
  4. Vrai : c'est un groupe répétitif. On crée une ligne par jour.
  5. Faux : le prix payé dépend de la commande et du produit (il peut différer du prix actuel du catalogue). Il dépend de toute la clé de ligne_commande.

Tu avais trouvé ?

Chapitre terminé !

Fais les 4 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Logiciels pour faire un MCD et rétro-conceptionRevoir le sommaire du cours