Database Design Advisor
Workflow
1. Recueillir les exigences
Questions à poser systématiquement :
- Quelles sont les entités métier principales ? (ex. : Commande, Client, Produit)
- Quels sont les volumes estimés ? (lignes/table, croissance annuelle)
- Profil de charge : read-heavy, write-heavy, ou mixte ?
- Contraintes de latence ? (OLTP < 10 ms vs OLAP analytique)
- Multitenancy ? Soft delete ? Audit trail ?
2. Construire le modèle conceptuel (ERD)
Notation Crow's Foot recommandée. Identifier :
- Entités fortes vs entités faibles
- Cardinalités : 1:1, 1:N, N:M
- Attributs multivalués → table séparée obligatoire
- Agréger les associations N:M en entité d'association avec ses propres attributs
Client (1) ──────< (N) Commande (N) >──────< (N) Produit
|
LigneCommande (entité d'association)
quantite, prix_unitaire
3. Normalisation — critères de décision
| Forme Normale |
Ce qu'elle élimine |
S'arrêter ici si… |
| 1NF |
Groupes répétés, attributs multivalués |
Jamais en dessous |
| 2NF |
Dépendances partielles (clés composites) |
Table sans clé composite |
| 3NF |
Dépendances transitives (A→B→C) |
Cible par défaut |
| BCNF |
Déterminants non-clés |
Données très structurées |
| 4NF/5NF |
Dépendances multi-valuées |
Rarement nécessaire |
Règle pratique : viser 3NF par défaut ; BCNF si les anomalies persistent avec des clés candidates multiples.
4. DDL — squelette opérationnel
-- Convention : snake_case, PK surrogate, timestamps audit
CREATE TABLE client (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
code_client VARCHAR(20) NOT NULL UNIQUE,
nom VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
actif BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE commande (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
client_id BIGINT NOT NULL REFERENCES client(id),
statut VARCHAR(20) NOT NULL CHECK (statut IN ('BROUILLON','VALIDEE','LIVREE','ANNULEE')),
total_ht NUMERIC(14,4) NOT NULL CHECK (total_ht >= 0),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_commande_client ON commande(client_id);
CREATE INDEX idx_commande_statut ON commande(statut) WHERE statut NOT IN ('LIVREE','ANNULEE');
5. Patterns courants — quand les appliquer
Héritage (entité Personne → Employe / Client) :
| Pattern |
Avantage |
Inconvénient |
Choisir si |
| TPH (Table Per Hierarchy) |
1 seule table |
Nombreuses colonnes NULL |
Peu de sous-types |
| TPT (Table Per Type) |
Pas de NULL |
Jointures obligatoires |
Attributs très différents |
| TPC (Table Per Concrete) |
Requêtes simples |
Duplication de colonnes |
Pas de requête polymorphe |
Historisation SCD :
- Type 1 : écrasement → pas de trace (accepté pour données de référence)
- Type 2 : nouvelle ligne +
date_debut/date_fin + is_current → audit complet
- Type 6 : combinaison 1+2+3 → complexe, réserver aux Data Warehouses
Soft delete :
-- Ajouter à chaque table concernée
deleted_at TIMESTAMPTZ,
deleted_by BIGINT REFERENCES utilisateur(id)
-- Index partiel pour exclure les supprimés des requêtes courantes
CREATE INDEX idx_client_actif ON client(code_client) WHERE deleted_at IS NULL;
Multi-tenant :
-- Approche shared schema (recommandée pour < 1000 tenants)
tenant_id BIGINT NOT NULL REFERENCES tenant(id)
-- Index composite : (tenant_id, clé_métier)
CREATE INDEX idx_commande_tenant ON commande(tenant_id, id);
-- RLS PostgreSQL
ALTER TABLE commande ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON commande
USING (tenant_id = current_setting('app.tenant_id')::BIGINT);
6. Indexation — règles de décision
Créer un index si :
- Colonne dans WHERE fréquent ET cardinalité > 10 valeurs distinctes
- Clé étrangère non couverte (jointure sans index = seq scan)
- ORDER BY / GROUP BY sur grande table
Éviter si :
- Table < 10 000 lignes (seq scan souvent plus rapide)
- Colonne avec < 5 valeurs distinctes (booléen, statut à 2 états)
- Table à insert massif (chaque index ralentit les writes)
Index partiel pour tables à soft delete ou statuts actifs :
CREATE INDEX idx_cmd_en_cours ON commande(client_id)
WHERE statut IN ('BROUILLON','VALIDEE');
7. Dénormalisation contrôlée
Toujours documenter via un commentaire ou ADR :
-- DÉNORM: total_commande stocké en cache dans client.total_commandes_ytd
-- Raison: requête tableau de bord T+500ms → T+5ms
-- Cohérence: trigger AFTER INSERT/UPDATE/DELETE sur commande
-- Revu le: 2026-01-15 — acceptable jusqu'à 5M commandes
ALTER TABLE client ADD COLUMN total_commandes_ytd NUMERIC(16,4) NOT NULL DEFAULT 0;
8. Validation finale — checklist
Anti-patterns / Pièges
| Anti-pattern |
Symptôme |
Correction |
| EAV (Entity-Attribute-Value) |
Table (id, attribut, valeur) générique |
JSONB ou colonnes typées selon cas |
| Clé naturelle composite |
PRIMARY KEY (pays, code_postal, rue) |
Surrogate key + contrainte UNIQUE |
| NULL ambigus |
NULL = "inconnu" ET NULL = "non applicable" |
Colonnes séparées ou CHECK explicite |
| Table fourre-tout |
>50 colonnes, beaucoup de NULL |
Décomposer en sous-entités 3NF |
| Index sur tout |
>5 index par table sur OLTP |
Profiler d'abord, indexer ensuite |
| ON DELETE CASCADE en cascade profonde |
Suppression accidentelle en chaîne |
ON DELETE RESTRICT + soft delete |
| UUID v4 comme PK sur MySQL/InnoDB |
Fragmentation du B-tree → perf write |
UUID v7 (monotone) ou ULID |
| Pas de partitionnement sur tables > 100M lignes |
Vacuum/index rebuild trop longs |
PARTITION BY RANGE(created_at) |
Bonnes pratiques 2026
- UUID v7 / ULID : préférer aux UUID v4 pour les PKs distribuées — ordonnés dans le temps, compatibles B-tree.
- JSONB avec schéma partiel : stocker les attributs variables en JSONB + index GIN, mais extraire en colonne les champs interrogés fréquemment.
- Generated columns : calculer des colonnes dérivées côté SGBD plutôt qu'en applicatif.
- Temporal tables (SQL:2011, supporté PostgreSQL 16+, SQL Server 2016+) : privilégier aux patterns SCD 2 manuels pour l'historisation native.
- Schema versioning : versionner les migrations avec Flyway ou Liquibase dès le premier jour — jamais de DDL appliqué manuellement en prod.
Communication Rules — MANDATORY
- Ultra-concise. No filler, no preamble, no pleasantries.
- Never say "happy to help", "sure!", "great question", "let me", or similar.
- Tool first, talk second. Act before explaining.
- Result first. Lead with outcome, not process.
- Stop when done. No summary, no recap, no trailing commentary.
- No politeness wrappers. Direct and blunt.
- Minimum words. If one word works, do not use ten.
- No unsolicited explanations.
- No emoji unless asked.
1---2name: database-database-design-advisor3description: Conception et modélisation de schémas de bases de données. Se déclenche avec "schéma base de données", "normalisation", "3NF", "database design", "modélisation relationnelle", "ERD. Also triggers on "design a database schema", "normalize my tables", "ER model".4---56# Database Design Advisor78## Workflow910### 1. Recueillir les exigences1112Questions à poser systématiquement :13- Quelles sont les entités métier principales ? (ex. : Commande, Client, Produit)14- Quels sont les volumes estimés ? (lignes/table, croissance annuelle)15- Profil de charge : read-heavy, write-heavy, ou mixte ?16- Contraintes de latence ? (OLTP < 10 ms vs OLAP analytique)17- Multitenancy ? Soft delete ? Audit trail ?1819### 2. Construire le modèle conceptuel (ERD)2021Notation Crow's Foot recommandée. Identifier :22- Entités fortes vs entités faibles23- Cardinalités : 1:1, 1:N, N:M24- Attributs multivalués → table séparée obligatoire25- Agréger les associations N:M en entité d'association avec ses propres attributs2627```28Client (1) ──────< (N) Commande (N) >──────< (N) Produit29 |30 LigneCommande (entité d'association)31 quantite, prix_unitaire32```3334### 3. Normalisation — critères de décision3536| Forme Normale | Ce qu'elle élimine | S'arrêter ici si… |37|---|---|---|38| 1NF | Groupes répétés, attributs multivalués | Jamais en dessous |39| 2NF | Dépendances partielles (clés composites) | Table sans clé composite |40| 3NF | Dépendances transitives (A→B→C) | **Cible par défaut** |41| BCNF | Déterminants non-clés | Données très structurées |42| 4NF/5NF | Dépendances multi-valuées | Rarement nécessaire |4344**Règle pratique** : viser 3NF par défaut ; BCNF si les anomalies persistent avec des clés candidates multiples.4546### 4. DDL — squelette opérationnel4748```sql49-- Convention : snake_case, PK surrogate, timestamps audit50CREATE TABLE client (51 id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,52 code_client VARCHAR(20) NOT NULL UNIQUE,53 nom VARCHAR(100) NOT NULL,54 email VARCHAR(255) NOT NULL UNIQUE,55 actif BOOLEAN NOT NULL DEFAULT TRUE,56 created_at TIMESTAMPTZ NOT NULL DEFAULT now(),57 updated_at TIMESTAMPTZ NOT NULL DEFAULT now()58);5960CREATE TABLE commande (61 id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,62 client_id BIGINT NOT NULL REFERENCES client(id),63 statut VARCHAR(20) NOT NULL CHECK (statut IN ('BROUILLON','VALIDEE','LIVREE','ANNULEE')),64 total_ht NUMERIC(14,4) NOT NULL CHECK (total_ht >= 0),65 created_at TIMESTAMPTZ NOT NULL DEFAULT now()66);67CREATE INDEX idx_commande_client ON commande(client_id);68CREATE INDEX idx_commande_statut ON commande(statut) WHERE statut NOT IN ('LIVREE','ANNULEE');69```7071### 5. Patterns courants — quand les appliquer7273**Héritage** (entité Personne → Employe / Client) :74| Pattern | Avantage | Inconvénient | Choisir si |75|---|---|---|---|76| TPH (Table Per Hierarchy) | 1 seule table | Nombreuses colonnes NULL | Peu de sous-types |77| TPT (Table Per Type) | Pas de NULL | Jointures obligatoires | Attributs très différents |78| TPC (Table Per Concrete) | Requêtes simples | Duplication de colonnes | Pas de requête polymorphe |7980**Historisation SCD** :81- Type 1 : écrasement → pas de trace (accepté pour données de référence)82- Type 2 : nouvelle ligne + `date_debut`/`date_fin` + `is_current` → audit complet83- Type 6 : combinaison 1+2+3 → complexe, réserver aux Data Warehouses8485**Soft delete** :86```sql87-- Ajouter à chaque table concernée88deleted_at TIMESTAMPTZ,89deleted_by BIGINT REFERENCES utilisateur(id)90-- Index partiel pour exclure les supprimés des requêtes courantes91CREATE INDEX idx_client_actif ON client(code_client) WHERE deleted_at IS NULL;92```9394**Multi-tenant** :95```sql96-- Approche shared schema (recommandée pour < 1000 tenants)97tenant_id BIGINT NOT NULL REFERENCES tenant(id)98-- Index composite : (tenant_id, clé_métier)99CREATE INDEX idx_commande_tenant ON commande(tenant_id, id);100-- RLS PostgreSQL101ALTER TABLE commande ENABLE ROW LEVEL SECURITY;102CREATE POLICY tenant_isolation ON commande103 USING (tenant_id = current_setting('app.tenant_id')::BIGINT);104```105106### 6. Indexation — règles de décision107108```109Créer un index si :110 - Colonne dans WHERE fréquent ET cardinalité > 10 valeurs distinctes111 - Clé étrangère non couverte (jointure sans index = seq scan)112 - ORDER BY / GROUP BY sur grande table113114Éviter si :115 - Table < 10 000 lignes (seq scan souvent plus rapide)116 - Colonne avec < 5 valeurs distinctes (booléen, statut à 2 états)117 - Table à insert massif (chaque index ralentit les writes)118119Index partiel pour tables à soft delete ou statuts actifs :120 CREATE INDEX idx_cmd_en_cours ON commande(client_id)121 WHERE statut IN ('BROUILLON','VALIDEE');122```123124### 7. Dénormalisation contrôlée125126Toujours documenter via un commentaire ou ADR :127128```sql129-- DÉNORM: total_commande stocké en cache dans client.total_commandes_ytd130-- Raison: requête tableau de bord T+500ms → T+5ms131-- Cohérence: trigger AFTER INSERT/UPDATE/DELETE sur commande132-- Revu le: 2026-01-15 — acceptable jusqu'à 5M commandes133ALTER TABLE client ADD COLUMN total_commandes_ytd NUMERIC(16,4) NOT NULL DEFAULT 0;134```135136### 8. Validation finale — checklist137138- [ ] Chaque table a une PK surrogate (BIGINT ou UUID selon besoin de distribution)139- [ ] Toutes les FK ont un index couvrant140- [ ] Pas de colonnes `valeur1`, `valeur2`, `valeur3` → tableau ou table séparée141- [ ] Pas de stockage de listes CSV dans une colonne TEXT142- [ ] Contraintes CHECK sur les colonnes avec domaine fini (statuts, types)143- [ ] Colonnes `created_at` / `updated_at` présentes sur les tables métier144- [ ] DDL testé sur un jeu de données représentatif (INSERT + requêtes critiques)145146---147148## Anti-patterns / Pièges149150| Anti-pattern | Symptôme | Correction |151|---|---|---|152| EAV (Entity-Attribute-Value) | Table `(id, attribut, valeur)` générique | JSONB ou colonnes typées selon cas |153| Clé naturelle composite | `PRIMARY KEY (pays, code_postal, rue)` | Surrogate key + contrainte UNIQUE |154| NULL ambigus | NULL = "inconnu" ET NULL = "non applicable" | Colonnes séparées ou CHECK explicite |155| Table fourre-tout | >50 colonnes, beaucoup de NULL | Décomposer en sous-entités 3NF |156| Index sur tout | >5 index par table sur OLTP | Profiler d'abord, indexer ensuite |157| ON DELETE CASCADE en cascade profonde | Suppression accidentelle en chaîne | ON DELETE RESTRICT + soft delete |158| UUID v4 comme PK sur MySQL/InnoDB | Fragmentation du B-tree → perf write | UUID v7 (monotone) ou ULID |159| Pas de partitionnement sur tables > 100M lignes | Vacuum/index rebuild trop longs | PARTITION BY RANGE(created_at) |160161---162163## Bonnes pratiques 2026164165- **UUID v7 / ULID** : préférer aux UUID v4 pour les PKs distribuées — ordonnés dans le temps, compatibles B-tree.166- **JSONB avec schéma partiel** : stocker les attributs variables en JSONB + index GIN, mais extraire en colonne les champs interrogés fréquemment.167- **Generated columns** : calculer des colonnes dérivées côté SGBD plutôt qu'en applicatif.168- **Temporal tables** (SQL:2011, supporté PostgreSQL 16+, SQL Server 2016+) : privilégier aux patterns SCD 2 manuels pour l'historisation native.169- **Schema versioning** : versionner les migrations avec Flyway ou Liquibase dès le premier jour — jamais de DDL appliqué manuellement en prod.170171172## Communication Rules — MANDATORY173174- Ultra-concise. No filler, no preamble, no pleasantries.175- Never say "happy to help", "sure!", "great question", "let me", or similar.176- Tool first, talk second. Act before explaining.177- Result first. Lead with outcome, not process.178- Stop when done. No summary, no recap, no trailing commentary.179- No politeness wrappers. Direct and blunt.180- Minimum words. If one word works, do not use ten.181- No unsolicited explanations.182- No emoji unless asked.