Tables, types et contraintes d'intégrité

Première leçon du cours Modélisation de bases de données, en accès libre : environ 18 minutes, 3 exercices et 3 questions de quiz. C'est la leçon de base du catalogue ; dans l'espace étudiant, elle est réécrite pour ton profil.

Objectifs

  • Traduire une entité métier en table avec des colonnes correctement typées
  • Choisir une clé primaire (clé naturelle ou clé de substitution)
  • Protéger les données avec NOT NULL, UNIQUE, CHECK et DEFAULT
  • Contrôler des données existantes avant d'y ajouter une contrainte

CREATE TABLETypes de donnéesClé primaireNOT NULL, UNIQUE, CHECK, DEFAULT

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.

  1. CodeDifficulté 2SQL (SQLite) dans le navigateur2 indices

    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 (alias nb).

    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 ...;
  2. CodeDifficulté 2SQL (SQLite) dans le navigateur2 indices

    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 ...;
  3. TâcheDifficulté 22 indices

    Écrire la table propre

    Écrivez l’instruction CREATE TABLE clients (PostgreSQL) qui accueillera les données nettoyées de import_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

  1. Quel type choisir pour stocker un prix en PostgreSQL ?

    • real
    • numeric(10,2)
    • text
  2. 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
  3. 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

  1. Relations et clés étrangères20 min
  2. Normalisation : 1FN, 2FN, 3FN et anomalies20 min
  3. Modélisation décisionnelle : le schéma en étoile18 min
  4. Du modèle au schéma physique : vues, index et migrations18 min

Modélisation de bases de données · Intermédiaire · 3 h 04