Normalisation

Normalisation

Normalisation est le processus de restructuration de nos entités et leurs attributs. Il y a plusieurs objectifs :

  • la minimisation de redondance dans notre schĂ©ma

  • la dĂ©tection et rĂ©solution des problĂšmes de consistance

  • ...

Nous faisons évoluer notre schéma de façon itérative, en passant par plusieurs phases de normalisation :

  • 1NF – First normal form

  • 2NF – Second normal form

  • 3NF – Third normal form

  • BCNF – Boyce-Cobbs normal form

Normalisation

1NF

Une entitĂ© est en 1NF s’il n’y a pas d’attribut composĂ© ou multivaleur

Par exemple :

étudiant(id, nom, prénom, adresse)

L'entitĂ© Ă©tudiant n'est pas en 1NF parce que l'adresse peut ĂȘtre considĂ©rĂ©e comme « composĂ© » de numĂ©ro, rue, city, dĂ©partement, rĂ©gion, pays, code postal.

Nous faisons évoluer le schéma de l'étudiant :

étudiant(id, nom, prénom, addr_numéro, addr_rue, addr_city, addr_dept, addr_region, addr_pays, addr_code)

1NF nous aide à se sécuriser pour le futur

Imaginons le cas suivant : on est en train de crĂ©er une appli pour grand public. Nos utilisateurs indiquent leur adresse, qui pour le moment est juste "indicatif". Donc au dĂ©part, on se dit, "pas la peine de se prendre la tĂȘte avec plusieurs colonnes pour chaque composant de l'adresse. Cela ne change rien pour l'utilisateur".

Sauf, aprÚs quelques mois de fonctionnement, on ajoute un service de livraison (qui n'était pas prévu au départ de projet). Il faut optimiser les livraisons par département. Mince ! Il serait difficile d'extraire l'info nécessaire, car le numéro de département et confondu dans l'adresse !

Un autre exemple. On a une entité qui représente nos étudiants et leurs cours.

étudiant(numéro, nom, cours)
numéronomcours

1

Bonnetain

[ SGBDR, Backend ]

2

Laurendeau

[ Angular ]

3

Perrousset

[ Backend, Angular ]

On décide de lister les cours de chaque utilisateur dans un seul champ cours. Le type de l'attribut cours est un array des noms des cours. Donc, ici on a un problÚme de domaine.

Certes, on utilise un format structuré (pseudo-json). Cette table n'est pas en 1NF. La colonne cours est multi-valeur. Dans ce cas, il serait difficile d'éxtraire tous les étudiants du cours Backend, par exemple.

Il faut modifier le type (et donc restreindre le domaine) de l'attribut cours : on l'imite à un seul cours. Nos données se transforment donc ainsi :

numéronomcours

1

Bonnetain

SGBDR

1

Bonnetain

Backend

2

Laurendeau

Angular

3

Perrousset

Backend

3

Perrousset

Angular

Cette table est maintenant en 1NF.

Il est toujours utile de tester nos entités avec des données d'échantillon ! On se rend compte des failles plus facilement !

2NF

Une entité est en 2NF si :

  • elle est dĂ©jĂ  en 1NF

  • elle n’a pas de dĂ©pendance partielle, c'est-Ă -dire il n’y a pas d’attribut non-prime qui dĂ©pend d’un sous-set d’une clĂ© candidate. En langage plus simple : il ne faut pas qu'il y a des attributs qui dĂ©pendent entiĂšrement sur un sous-set de la clĂ© primaire.

Exemple, on aimerait afficher le tarif des cours des Ă©tudiants :

étudiant(numéro, nom, cours, prix)

En échantillon des données possibles est :

numéronomcoursprix

1

Bonnetain

SGBDR

1000

1

Bonnetain

Backend

1500

2

Laurendeau

Angular

2000

3

Perrousset

Backend

1500

3

Perrousset

Angular

2000

Ici, le prix dĂ©pend du cours, donc il y a une dĂ©pendance fonctionnelle cours ➔ prix.

  • La seule clĂ© candidate est { numĂ©ro, cours }

  • prix est donc un attribut non-prime (il ne fait pas partie de la clĂ© candidate)

  • prix dĂ©pend d’un sous-set de la clĂ© candidate (cours), et donc il y a une dĂ©pendance partielle

  • alors cette table n’est pas en 2NF

En réalité, il est plutÎt intuitif que cette représentation n'est pas idéal. Il suffit de se poser la question : "et si demain le prix d'un cours change ?". La réponse, je serais obligé de modifier beaucoup de lignes, alors que la modification devrait impacter une seule ligne.

Quelle est la solution ? Il faut couper notre entité en deux.

étudiant(numéro, nom, cours)
cours(cours, prix)

Exemple de la table Ă©tudiant :

numéronomcours

1

Bonnetain

SGBDR

1

Bonnetain

Backend

2

Laurendeau

Angular

3

Perrousset

Backend

3

Perrousset

Angular

Exemple de la table cours :

coursprix

SGBDR

1000

Backend

1500

Angular

2000

Est-ce que nos entités sont tous en 2NF ? Non ! Il existe encore une dépendance partielle !

  • l'attribut nom (non-prime) dĂ©pend de numĂ©ro, et numĂ©ro fait partie de notre clĂ©-candidate { numĂ©ro, cours }

Il faut encore couper en deux les entités :

étudiant(numéro, nom)
étudiant_cours(numéro, cours)
cours(cours, prix)

Exemple de la table Ă©tudiant :

numéronom

1

Bonnetain

2

Laurendeau

3

Perrousset

Exemple de la table Ă©tudiant_cours :

numérocours

1

SGBDR

1

Backend

2

Angular

3

Backend

3

Angular

Exemple de la table cours :

coursprix

SGBDR

1000

Backend

1500

Angular

2000

3NF

Une entité est en 3NF si :

  • elle est en 2NF

  • Il n’y a pas de dĂ©pendance transitive pour les attributs non-primes

  • Un attribut non-prime ne peut pas ĂȘtre dĂ©pendant d’un autre attribut non-prime

Qu'est-ce que c'est une dépendance transitive ?

Dans un set de relations fonctionnelles :

{ 
  A ➔ B
  B ➔ C 
} 

Une dĂ©pendance transitive existe : A ➔ C

Exemple d'une entité étudiant :

étudiant(id, nom, prénom, téléphone, département, région, pays, ùge)
  • elle est en 1NF, car il n'y a pas d'attribut composĂ© ou multivaleur

  • elle est en 2NF parce qu'il n'y a pas de dĂ©pendance partielle (la clĂ© candidate est id uniquement)

  • elle n'est pas en 3NF parce qu'il existe une dĂ©pendance transitive !

Pourquoi ?

Parmi les dépendances fonctionnelles, on trouve :

id ➔ dĂ©partement
dĂ©partement ➔ rĂ©gion
rĂ©gion ➔ pays

Il y a donc une dépendance transitive entre id et pays !

La solution, encore une fois, est de couper l'entité en deux :

étudiant(id, nom, prénom, téléphone, département, région, ùge)
région_pays(région, pays)

Est-ce qu'on a atteint la 3NF ? Pas encore ! Il y a toujours des dépendances transitives à résoudre :

étudiant(id, nom, prénom, téléphone, département, ùge)
département_région(département, région)
région_pays(région, pays)

Au-delĂ  de 3NF

D’autres formes normalisĂ©es qui ajoutent de contraintes sur les donnĂ©es :

  • BCNF : boyce-codd normal form (une forme plus restreinte de 3NF)

  • 4NF

  • 5NF

  • etc..

Je vous laisse Ă©tudier ces formes plus strictes, par exemple ici

DĂ©normalisation

Plus on normalise, plus on serait obligé de faire les jointures de table pour récupérer les données :

  • RequĂȘtes SQL complexes

  • ExĂ©cution plus lente

En plus, le passage en BCNF pourrait aussi casser des relations fonctionnelles.

Le processus de dĂ©-normalisation introduit quelques donnĂ©es redondantes dans l’intĂ©rĂȘt de performance.

Exemple :

user(user_id, email, nom, prénom)
post(post_id, user_id, contenu, date)
post_commentaire(commentaire_id, post_id, contenu, date)

Si on voulait rĂ©cupĂ©rer tous les commentaires de tous les post d’un utilisateur, nous serions obligĂ©s de faire un join entre au moins post et post_commentaire

Si c’est une requĂȘte qu’on fait souvent, on pourrait optimiser en dĂ©normalisant, on ajoute user_id Ă  la table post_commentaire

post_commentaire(commentaire_id, post_id, user_id, contenu, date)

Maintenant, on peut simplement faire un select simple en filtrant par le user_id.

Comme d'habitude, on essaie de trouver le meilleur compromis entre plusieurs facteurs :

  • la consistance des donnĂ©es, manque de redondance

  • performance et optimisation

  • facilitĂ© de comprĂ©hension du code

  • flexibilitĂ© et adaptabilitĂ© longue terme

Observations

Un schéma n'est pas statique, il pourrait changer au fur et à mesure :

  • le schĂ©ma Ă©volue avec notre comprĂ©hension du problĂšme ou du domaine.

    • Parfois, c'est aprĂšs la mise en service de notre plateforme qu'on se rend compte !

  • avec l’évolution de notre logique business.

    • Les entreprises (surtout les startups) peuvent pivoter, changer de fonction. Il faut ĂȘtre prĂȘt Ă  s'adapter en fonction !

L’importance est de pouvoir :

  • communiquer l’information utile entre dĂ©veloppeurs

  • afin de pouvoir faire des requĂȘtes prĂ©cises

  • et efficaces

  • et manipuler les donnĂ©es sans crĂ©er des incohĂ©rences

Exercice

(basé sur une vraie startup auquel j'ai eu le plaisir de participer)

Vous lancez une appli qui va permettre aux utilisateurs de garder un journal des repas. À chaque repas, l’utilisateur ouvre l’appli et prĂ©cise des informations suivantes :

  • Quel repas (petit-dej, dej, etc)

  • Les aliments du repas

  • Photos

  • Les sensations pendant ou aprĂšs le repas (satisfaction, toujours faim, etc)

  • Un texte libre permettant de dĂ©crire d’autres infos concernant le repas

Le but de cette appli est de pouvoir aider aux gens, et leurs nutritionnistes, de comprendre plus précisément leur consommation :

  • Le profile des macro-aliments (glucides, gras, protĂ©ines) par repas et par jour

  • La totale calorifique par repas et par jour

  • Les sensations qui permettraient aux nutritionnistes et/ou psychologues d’adapter les rĂ©gimes alimentaires qui amĂ©liorent la vie quotidienne (et donc l’adhĂ©rence) de l’utilisateur

Utilisez les techniques de modélisation (entités, normalisation, etc) afin de modéliser une base de données pour cette application.

Le livrable est un schéma E-R (vous pouvez utiliser un des outils gratuits pour le produire)

Attention Ă  rĂ©duire autant possible la redondance ou risque d’erreurs.

DerniĂšre mise Ă  jour