Jusqu’ici, vous avez interrogé des tables existantes. Modéliser, c’est l’étape d’avant : décider quelles tables créer, avec quelles colonnes et quelles règles. Un bon modèle rend les requêtes simples et empêche les données incohérentes d’entrer ; un mauvais modèle oblige à corriger les données en permanence.
De l’entité à la table
On part du vocabulaire métier. Une entité est une chose dont on veut garder la trace : un client, un produit, une commande. Elle devient une table. Ses attributs (nom, email, date d’inscription) deviennent des colonnes, chacune avec un type.
CREATE TABLE clients (
id INTEGER PRIMARY KEY, -- identifiant unique
email TEXT NOT NULL UNIQUE, -- obligatoire et sans doublon
nom TEXT NOT NULL,
code_postal TEXT CHECK (length(code_postal) = 5),
date_inscription TEXT NOT NULL DEFAULT (date('now')),
actif INTEGER NOT NULL DEFAULT 1 CHECK (actif IN (0, 1))
);
Choisir les types
| Donnée | PostgreSQL | SQLite |
|---|---|---|
| Identifiant | bigint GENERATED ALWAYS AS IDENTITY |
INTEGER PRIMARY KEY |
| Texte | text ou varchar(n) |
TEXT |
| Montant | numeric(10,2) |
INTEGER en centimes |
| Date / horodatage | date, timestamptz |
TEXT au format ISO |
| Booléen | boolean |
INTEGER 0/1 |
Deux règles : un code postal ou un numéro de téléphone est du texte (un zéro initial ne doit pas disparaître), et un montant ne se stocke jamais en nombre à virgule flottante (real, float), qui produit des erreurs d’arrondi.
SQLite a un typage souple : une colonne INTEGER accepte un texte. Depuis la version 3.37, le mot-clé STRICT en fin de CREATE TABLE impose les types. PostgreSQL, lui, contrôle toujours les types.
La clé primaire
La clé primaire identifie chaque ligne : elle est unique et jamais nulle. Deux options :
- une clé naturelle, issue du métier (numéro de sécurité sociale, code ISO d’un pays) ;
- une clé de substitution, un identifiant sans signification généré par la base.
On préfère généralement la clé de substitution : un email ou un numéro client peuvent changer, et les modifier casserait toutes les références. La donnée métier reste protégée par une contrainte UNIQUE.
Les contraintes
NOT NULL: la valeur est obligatoire.UNIQUE: pas deux lignes avec la même valeur (plusieurs NULL restent autorisés).CHECK (condition): la condition doit être vraie (prix > 0,quantite BETWEEN 1 AND 100).DEFAULT valeur: valeur utilisée si l’insertion n’en fournit pas.
Les contraintes s’appliquent à toutes les applications qui écrivent dans la base : script d’import, back-office, API. C’est bien plus sûr qu’une vérification dans un seul formulaire.
Contrôler avant de contraindre
Sur une table déjà remplie, ajouter une contrainte échoue si des lignes la violent. On commence donc par les chercher :
-- Emails en double, sans tenir compte de la casse
SELECT lower(email) AS email_normalise, COUNT(*) AS nb
FROM import_clients
GROUP BY lower(email)
HAVING COUNT(*) > 1;
On corrige ou on fusionne les lignes, puis on ajoute la contrainte. En PostgreSQL : ALTER TABLE clients ADD CONSTRAINT clients_email_key UNIQUE (email);. SQLite ne permet pas d’ajouter une contrainte à une table existante : on recrée la table et on y copie les données.
Exercices
Les solutions et les tests restent sur le serveur. Dans l'espace étudiant, ton code est exécuté et corrigé, et le tuteur donne des indices avant la solution.
Doublons d'email avant contrainte UNIQUE
Table disponible :
import_clients(id, email, nom, code_postal), importée sans contrainte.Avant d’ajouter une contrainte d’unicité sur l’email, listez les emails en double sans tenir compte de la casse : affichez l’email en minuscules (alias
email_normalise) et le nombre d’occurrences (aliasnb).Données de l'exercice (SQL)
-- Fichier importé tel quel, sans aucune contrainte CREATE TABLE import_clients ( id INTEGER, email TEXT, nom TEXT, code_postal TEXT ); INSERT INTO import_clients VALUES (1, '[email protected]', 'Léa', '69001'), (2, '[email protected]', 'Léa M.', '69001'), (3, '[email protected]', 'Marc', '75011'), (4, '[email protected]', 'Marc', '75011'), (5, NULL, 'Nina', '13008'), (6, 'sam.exemple.fr', 'Sam', '33000'), (7, '[email protected]', 'Inès', '5900'), (8, '[email protected]', 'Paul', '44000');SELECT lower(email) AS email_normalise, COUNT(*) AS nb FROM import_clients GROUP BY ... HAVING ...;Lignes qui violeraient les contraintes
Table disponible :
import_clients(id, email, nom, code_postal).On veut ajouter les règles : email obligatoire et contenant un
@précédé et suivi d’au moins un caractère, code postal de 5 caractères. Affichez l’id des lignes qui violent au moins une règle.Données de l'exercice (SQL)
-- Fichier importé tel quel, sans aucune contrainte CREATE TABLE import_clients ( id INTEGER, email TEXT, nom TEXT, code_postal TEXT ); INSERT INTO import_clients VALUES (1, '[email protected]', 'Léa', '69001'), (2, '[email protected]', 'Léa M.', '69001'), (3, '[email protected]', 'Marc', '75011'), (4, '[email protected]', 'Marc', '75011'), (5, NULL, 'Nina', '13008'), (6, 'sam.exemple.fr', 'Sam', '33000'), (7, '[email protected]', 'Inès', '5900'), (8, '[email protected]', 'Paul', '44000');SELECT id FROM import_clients WHERE email IS NULL OR ... OR ...;Écrire la table propre
Écrivez l’instruction
CREATE TABLE clients(PostgreSQL) qui accueillera les données nettoyées deimport_clients. Elle doit comporter une clé de substitution, un email obligatoire et unique, un nom obligatoire, un code postal de 5 caractères et une date de création remplie automatiquement.- La clé primaire est une colonne id générée automatiquement (GENERATED ALWAYS AS IDENTITY)
- email est NOT NULL et UNIQUE
- code_postal est de type texte avec un CHECK sur la longueur (ou char(5))
- Une colonne cree_le de type timestamptz a DEFAULT now()
- Aucun montant ni identifiant n'utilise un type flottant
Quiz de fin de leçon
Quel type choisir pour stocker un prix en PostgreSQL ?
- real
- numeric(10,2)
- text
Pourquoi préfère-t-on souvent une clé de substitution à l’email comme clé primaire ?
- Parce qu'un email peut changer et que la clé primaire est référencée ailleurs
- Parce qu'un email ne peut pas être unique
- Parce que les clés texte sont interdites
Une colonne UNIQUE peut-elle contenir plusieurs NULL ?
- Non, jamais
- Oui, NULL n'est égal à aucune valeur (comportement par défaut de PostgreSQL et SQLite)
- Seulement si la colonne est aussi NOT NULL
La suite du cours
- Relations et clés étrangères20 min
- Normalisation : 1FN, 2FN, 3FN et anomalies20 min
- Modélisation décisionnelle : le schéma en étoile18 min
- Du modèle au schéma physique : vues, index et migrations18 min