Menu

Contraintes CHECK SQLite : valider les données

Comment utiliser les contraintes CHECK en SQLite pour valider les valeurs : check sur une colonne, sur plusieurs colonnes, contraintes nommées et pièges courants.

Cette page contient des éditeurs exécutables — modifiez, exécutez et voyez la sortie instantanément.

La contrainte CHECK SQLite : une règle que chaque ligne doit respecter

Une contrainte CHECK SQLite, c'est tout simplement une expression booléenne que vous attachez à une table. À chaque INSERT et à chaque UPDATE, SQLite l'évalue, et si le résultat est faux, l'opération échoue. C'est la façon idéale d'inscrire une règle métier directement dans le schéma — du genre « le prix ne peut pas être négatif » ou « le statut doit faire partie de ces trois valeurs ».

Les deux premières lignes passent. La troisième déclenche CHECK constraint failed et se fait rejeter — la table ne la voit jamais. La contrainte s'applique à chaque écriture, que ça vienne de votre appli, d'un script de migration ou de quelqu'un qui bricole en ligne de commande.

CHECK au niveau colonne ou au niveau table

Une contrainte CHECK SQLite peut s'écrire à deux endroits : juste après la définition d'une colonne (niveau colonne), ou bien après toutes les colonnes (niveau table). Le comportement est identique ; ce qui change, c'est la lisibilité.

La première réservation passe. La seconde échoue — la date de fin est antérieure à la date de début. Les règles portant sur une seule colonne se lisent mieux au niveau colonne ; dès qu'il faut comparer deux colonnes ou plus, on les place au niveau table.

Restreindre les valeurs à une liste

Un cas classique consiste à forcer une colonne à prendre l'une des valeurs d'un ensemble figé. SQLite n'a pas de type enum natif, donc l'idiome reste CHECK ... IN (...) :

La troisième ligne échoue parce que 'pending' ne figure pas dans la liste autorisée. Si un jour vous devez ajouter un nouveau statut, il faudra reconstruire la table (on en reparle plus bas), donc réfléchissez bien avant de figer la liste. Cela dit, pour des vocabulaires vraiment fixes comme les noms de rôles ou les états de commande, c'est précisément la contrainte qu'il vous faut.

Nommer vos contraintes CHECK

Par défaut, une contrainte CHECK SQLite est anonyme. Le message d'erreur se contente d'afficher « CHECK constraint failed » suivi de l'expression — ça va quand il n'y a qu'un seul CHECK sur la table, mais ça devient vite confus quand vous en avez cinq. Pour donner un nom à une contrainte, utilisez CONSTRAINT :

Désormais, le message d'erreur contient le nom de la contrainte, donc vous savez tout de suite quelle règle a été violée. Nommer ses contraintes coûte quelques caractères en plus, mais c'est rentabilisé dès le premier incident en production.

CHECK et NULL : le piège classique

Une contrainte CHECK passe quand l'expression vaut vrai ou NULL. Elle ne bloque que sur un faux explicite. Ça paraît bizarre au premier abord, mais souviens-vous que quasiment toute comparaison avec NULL renvoie NULL, et non vrai ou faux.

La ligne avec NULL passe sans souci — NULL >= 0 renvoie NULL, et non false, donc la contrainte CHECK ne déclenche pas d'erreur. Si vous voulez vraiment interdire à la fois les valeurs négatives et les valeurs manquantes, il faut combiner NOT NULL avec la contrainte CHECK :

Maintenant, l'insertion échoue sur la contrainte NOT NULL avant même que le CHECK ne s'exécute. Les deux contraintes se complètent : NOT NULL gère l'absence de valeur, tandis que CHECK valide la forme des données.

Fonctions intégrées utiles dans une contrainte CHECK SQLite

L'expression d'un CHECK peut s'appuyer sur la plupart des fonctions intégrées de SQLite. En voici quelques-unes qui reviennent souvent :

Trois échecs : un format d'email invalide, un nom d'utilisateur trop court, et un code pays en minuscules. LIKE gère les motifs simples ; length(), upper(), lower() et les opérations arithmétiques sont tous permis. Veillez juste à garder l'expression déterministe — utiliser quelque chose comme random() ou current_timestamp dans un CHECK produit des règles qui peuvent varier d'une ligne à l'autre, ce qui est rarement le résultat recherché.

CHECK ou trigger SQLite : lequel choisir ?

CHECK et triggers peuvent tous deux rejeter des données invalides, et les débutants se demandent souvent vers lequel se tourner. La règle de base :

  • CHECK lorsque la règle ne dépend que de la ligne en cours d'écriture. « Cette colonne comparée à celle-là », « cette valeur dans un intervalle donné », « cette chaîne correspond à un motif ».
  • Trigger (concrètement un trigger BEFORE INSERT/UPDATE qui appelle RAISE) lorsque la règle dépend d'autres lignes, d'autres tables, ou demande quelque chose de plus complexe qu'une simple expression booléenne.

CHECK est plus rapide, plus simple, et visible directement dans le schéma — toute personne qui lit le CREATE TABLE voit la règle. Ne passez à un trigger que lorsque CHECK ne suffit plus à exprimer ce dont vous avez besoin.

Impossible de supprimer un CHECK avec ALTER

C'est le seul vrai point noir. SQLite n'a pas de ALTER TABLE ... DROP CONSTRAINT. Pour retirer ou modifier une contrainte CHECK, il faut reconstruire la table :

BEGIN;

CREATE TABLE products_new (
    id    INTEGER PRIMARY KEY,
    name  TEXT NOT NULL,
    price REAL NOT NULL CHECK (price >= 0 AND price <= 1000000)
);

INSERT INTO products_new SELECT * FROM products;
DROP TABLE products;
ALTER TABLE products_new RENAME TO products;

COMMIT;

Encapsule l'ensemble dans une transaction : comme ça, si quelque chose casse en cours de route, votre base reste intacte. Si d'autres tables ont des clés étrangères qui pointent vers la table que vous reconstruisez, la chorégraphie devient plus longue — il faut désactiver foreign_keys, reconstruire, réactiver, puis revérifier. On reviendra là-dessus plus tard dans le cours, dans la partie consacrée aux migrations.

La suite : les contraintes UNIQUE

CHECK valide la forme des valeurs au sein d'une même ligne. La prochaine contrainte, UNIQUE, valide quant à elle les relations entre les lignes — elle garantit que deux lignes ne partagent jamais la même valeur dans une colonne (ou un groupe de colonnes). On s'attaque à ça tout de suite après.

Questions fréquentes

Qu'est-ce qu'une contrainte CHECK en SQLite ?

Une contrainte CHECK, c'est une expression booléenne attachée à une table que chaque ligne doit respecter. SQLite l'évalue à chaque INSERT ou UPDATE et rejette l'opération si l'expression renvoie faux. C'est la façon la plus simple d'imposer une règle du genre « le prix doit être positif » sans écrire une ligne de code applicatif.

Une contrainte CHECK SQLite peut-elle porter sur plusieurs colonnes ?

Oui, mais il faut la déclarer au niveau de la table plutôt que sur une colonne précise. Par exemple, CHECK (start_date <= end_date) placée après la liste des colonnes peut référencer les deux. Techniquement, un CHECK de colonne peut aussi pointer vers d'autres colonnes, mais la version table-level reste bien plus lisible quand plusieurs colonnes sont en jeu.

Pourquoi ma contrainte CHECK ne se déclenche pas sur NULL ?

Un CHECK est validé quand l'expression vaut vrai ou NULL — il n'échoue que si elle vaut explicitement faux. Du coup, CHECK (age >= 0) accepte un age à NULL, parce que NULL >= 0 vaut NULL, pas faux. Si vous voulez aussi interdire NULL, ajoutez une contrainte NOT NULL à côté du CHECK.

Peut-on supprimer ou modifier une contrainte CHECK en SQLite ?

Pas directement. SQLite ne gère pas ALTER TABLE ... DROP CONSTRAINT. Pour modifier un CHECK, soit vous éditez sqlite_schema avec PRAGMA writable_schema (avancé, risqué), soit vous reconstruisez la table : créer une nouvelle table avec les bonnes contraintes, recopier les données, supprimer l'ancienne, puis renommer. Nommer ses contraintes rend ce script de reconstruction beaucoup plus lisible.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER