Normalisation
Dernière mise à jour
Dernière mise à jour
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
Par exemple :
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 :
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.
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 :
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 !
Exemple, on aimerait afficher le tarif des cours des étudiants :
En échantillon des données possibles est :
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.
Exemple de la table étudiant :
1
Bonnetain
SGBDR
1
Bonnetain
Backend
2
Laurendeau
Angular
3
Perrousset
Backend
3
Perrousset
Angular
Exemple de la table cours :
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 :
Exemple de la table étudiant :
1
Bonnetain
2
Laurendeau
3
Perrousset
Exemple de la table étudiant_cours :
1
SGBDR
1
Backend
2
Angular
3
Backend
3
Angular
Exemple de la table cours :
SGBDR
1000
Backend
1500
Angular
2000
Qu'est-ce que c'est une dépendance transitive ?
Dans un set de relations fonctionnelles :
Une dépendance transitive existe : A ➔ C
Exemple d'une entité étudiant :
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 :
Il y a donc une dépendance transitive entre id et pays !
La solution, encore une fois, est de couper l'entité en deux :
Est-ce qu'on a atteint la 3NF ? Pas encore ! Il y a toujours des dépendances transitives à résoudre :
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
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 :
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
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
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
(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.