Kodokon kodokon.com

Modélisation : normalisation et dénormalisation

Structurez vos schémas de la 1NF à la 3NF pour éliminer les anomalies, et sachez quand dénormaliser en connaissance de cause.

10 min · 3 questions

Ouvrir cette leçon dans Kodokon

Un mauvais schéma se paie pendant des années. La normalisation vise une chose : que chaque fait ne soit stocké qu'une seule fois, pour éliminer trois anomalies : de mise à jour (modifier un e-mail à dix endroits), d'insertion (impossible d'ajouter un produit sans commande) et de suppression (supprimer la dernière commande efface le client). Observez ce schéma volontairement raté.

SQL
CREATE TABLE bad_orders (
  id INTEGER PRIMARY KEY,
  customer_name TEXT,
  customer_email TEXT,
  product_names TEXT,
  total REAL
);

INSERT INTO bad_orders
  (customer_name, customer_email,
   product_names, total)
VALUES
  ('Alice', 'alice@mail.com',
   'keyboard,mouse', 74.0),
  ('Alice', 'alice@mail.co',
   'monitor', 199.0);
Liste dans une colonne, e-mail dupliqué avec une faute : tout est là.

Les trois premières formes normales, en pratique. 1NF : valeurs atomiques - product_names = 'keyboard,mouse' la viole, rendant tout WHERE ou JOIN sur les produits impraticable. 2NF (clé composée) : chaque colonne dépend de toute la clé - stocker le prix catalogue du produit dans une ligne de commande identifiée par (commande, produit) la viole, car le prix ne dépend que du produit. 3NF : aucune colonne non clé ne dépend d'une autre colonne non clé - customer_email qui dépend de customer_name la viole ; d'où le doublon alice@mail.co. Voici le schéma corrigé.

SQL
CREATE TABLE customers (
  id INTEGER PRIMARY KEY,
  name TEXT NOT NULL,
  email TEXT NOT NULL UNIQUE
);

CREATE TABLE products (
  id INTEGER PRIMARY KEY,
  name TEXT NOT NULL,
  price REAL NOT NULL
);

CREATE TABLE purchases (
  id INTEGER PRIMARY KEY,
  customer_id INTEGER NOT NULL
    REFERENCES customers(id)
);

CREATE TABLE purchase_items (
  purchase_id INTEGER NOT NULL
    REFERENCES purchases(id),
  product_id INTEGER NOT NULL
    REFERENCES products(id),
  quantity INTEGER NOT NULL,
  PRIMARY KEY (purchase_id, product_id)
);
Chaque fait vit à un seul endroit ; les clés étrangères relient le tout.
SQL
PRAGMA foreign_keys = ON;

INSERT INTO customers (name, email)
VALUES ('Alice', 'alice@mail.com');

INSERT INTO products (name, price)
VALUES ('keyboard', 49.0), ('mouse', 25.0);

INSERT INTO purchases (customer_id) VALUES (1);

INSERT INTO purchase_items
  (purchase_id, product_id, quantity)
VALUES (1, 1, 1), (1, 2, 1);

SELECT c.name, p.name AS product,
       i.quantity, p.price
FROM purchase_items AS i
JOIN purchases AS pu ON pu.id = i.purchase_id
JOIN customers AS c ON c.id = pu.customer_id
JOIN products AS p ON p.id = i.product_id;
Le détail complet se reconstruit par jointures.

Alors, quand dénormaliser ? Quand la lecture devient le goulot mesuré : jointures à cinq tables sur un écran critique, agrégat recalculé à chaque affichage. Les patterns honnêtes : une colonne de cache (purchases.total figé à la commande - d'ailleurs légitime : le prix payé est un fait historique, distinct du prix catalogue), un compteur maintenu par trigger, ou une table de reporting reconstruite par batch. Le prix à payer : chaque copie peut diverger, et c'est votre code qui devient garant de la cohérence, plus le moteur.

Quiz de validation

Vérifiez que vous avez bien retenu les points clés de cette leçon.

  1. La colonne product_names contenant 'keyboard,mouse' viole quelle forme normale ?
    • 1NF
    • 2NF
    • 3NF
  2. Quel symptôme signale une violation de la 3NF ?
    • Une clé primaire composée de deux colonnes
    • Une colonne non clé qui dépend d'une autre colonne non clé
    • Une table sans aucun index secondaire
  3. Quand la dénormalisation est-elle justifiée ?
    • Dès la conception, pour économiser des jointures futures
    • Après mesure, sur des lectures critiques, en assumant le risque d'incohérence
    • Jamais : un schéma doit rester en 3NF quoi qu'il arrive