Une colonne générée, c'est une colonne calculée
Une colonne générée SQLite est une colonne dont la valeur provient d'une expression, et non d'un INSERT. Vous écrivez la formule une seule fois dans le CREATE TABLE, et SQLite s'occupe du reste. Impossible d'y écrire directement — toute tentative déclenche une erreur.
Voici l'exemple le plus court possible :
total n'a jamais été inséré, et pourtant il apparaît dans chaque ligne. SQLite le recalcule à partir de price + tax à chaque lecture de la ligne. Modifiez l'une des deux colonnes et total suit automatiquement.
Le mot-clé GENERATED ALWAYS AS est obligatoire. Le ALWAYS est une formalité du standard SQL — SQLite ne propose pas d'autre option de toute façon.
SQLite VIRTUAL vs STORED
Chaque colonne générée existe sous l'une de ces deux variantes. Par défaut, c'est VIRTUAL :
Le modèle mental :
VIRTUAL— zéro octet sur le disque, mais du CPU à chaque lecture. Pas cher à ajouter, pas cher à modifier plus tard.STORED— occupe de l'espace disque, mais ne coûte rien de plus à la lecture. Rentable quand l'expression est coûteuse ou que la colonne est lue beaucoup plus souvent qu'elle n'est écrite.
Si vous ne précisez pas le mot-clé, vous obtenez VIRTUAL. Et c'est presque toujours le bon choix par défaut.
L'intérêt : indexer des valeurs dérivées
L'atout maître, c'est qu'on peut poser un index sur une colonne générée. Résultat : des recherches rapides sur des valeurs dérivées, sans avoir à réécrire toutes vos requêtes.
Imaginons que vous vouliez faire des recherches d'e-mails insensibles à la casse :
L'index couvre la version en minuscules. Une requête qui filtre sur email_lower utilise directement l'index. SQLite propose aussi des index sur expression (CREATE INDEX ... ON users(lower(email))), mais une colonne générée a l'avantage d'exposer la valeur calculée comme une vraie colonne : on peut la SELECT, la référencer dans des vues et la réutiliser depuis le code applicatif.
Extraire des valeurs depuis du JSON
Les colonnes générées SQLite prennent tout leur sens avec du JSON. Le support JSON de SQLite fournit l'opérateur ->> pour extraire une valeur scalaire ; encapsulez-la dans une colonne générée et vous obtenez un champ typé et indexable par-dessus un blob flexible.
user_id et kind se comportent comme des colonnes normales dans vos requêtes, mais les données vivent en réalité dans payload. Modifiez le JSON, et les colonnes se mettent à jour automatiquement. L'index sur user_id rend la recherche rapide.
Règles et contraintes
Quelques points que SQLite impose — autant les connaître avant de tomber dessus :
- L'expression doit être déterministe.
random(),datetime('now')et autres fonctions non déterministes sont interdites. La valeur doit pouvoir être reproduite à partir de la ligne. - L'expression ne peut référencer que les colonnes de la même ligne. Pas de sous-requêtes, pas d'agrégats, pas d'autres tables.
- Impossible de faire un
INSERTou unUPDATEdirectement sur une colonne générée.INSERT INTO products (total) VALUES (5)renvoie une erreur. - Les colonnes
STOREDne peuvent pas être ajoutées viaALTER TABLE ... ADD COLUMN. Seules les colonnesVIRTUALpeuvent être ajoutées après coup. - Les colonnes générées acceptent les contraintes
NOT NULL,CHECK,UNIQUEet mêmeFOREIGN KEY. De ce point de vue, elles se comportent comme n'importe quelle autre colonne.
Une petite démo de la règle d'écriture :
sqlite> INSERT INTO products (price, tax, total) VALUES (10, 1, 999);
Runtime error: cannot INSERT into generated column "total"
La solution : retirez la colonne générée de la liste du INSERT et laissez SQLite la calculer.
Choisir entre VIRTUAL et STORED
Le choix se joue généralement sur le ratio lecture/écriture et le coût de l'expression :
Quelques règles pratiques :
- Par défaut, prends
VIRTUAL. Ça ne coûte rien à l'écriture et ça suffit dans la grande majorité des cas. - Passe à
STOREDquand vous posez un index sur la colonne d'une table très sollicitée en écriture (l'index doit de toute façon persister la valeur), ou quand l'expression est vraiment coûteuse à calculer. - Ne te prends pas trop la tête. Le type fait partie du schéma, mais rien ne t'empêche de supprimer puis recréer la colonne si vous changez d'avis — du moins pour les
VIRTUAL.
Colonnes générées ou vues : que choisir ?
Le recouvrement avec les vues est réel : les deux exposent des valeurs calculées sans forcément les stocker. Voici comment on tranche en général :
- Une colonne générée appartient à une seule ligne d'une seule table. Elle sert aux dérivations ligne par ligne — formater un e-mail, extraire un champ JSON, calculer un total.
- Une vue, c'est une requête sauvegardée. Tu la sors quand le calcul implique des jointures, des agrégations ou des filtres sur plusieurs lignes.
Et vous pouvez combiner les deux. Une vue peut très bien faire un SELECT sur une table qui contient des colonnes générées, puis joindre d'autres données autour. Les colonnes générées vivent au niveau du stockage ; les vues, au niveau de la requête.
La suite : ATTACH DATABASE
Avec les colonnes générées, une table calcule ses propres valeurs. La page suivante prend le chemin inverse : connecter plusieurs bases SQLite en même temps avec ATTACH DATABASE, pour qu'une seule requête puisse traverser plusieurs fichiers.
Questions fréquentes
Qu'est-ce qu'une colonne générée en SQLite ?
Une colonne générée, c'est une colonne dont la valeur est calculée à partir d'une expression qui s'appuie sur d'autres colonnes de la même ligne. On la déclare avec GENERATED ALWAYS AS (expression) dans le CREATE TABLE. Vous n'écrivez jamais dedans directement : SQLite calcule la valeur tout seul, à la lecture ou à l'écriture de la ligne.
Quelle différence entre une colonne générée VIRTUAL et STORED ?
Une colonne VIRTUAL est recalculée à chaque lecture et ne prend aucune place sur disque — c'est le comportement par défaut. Une colonne STORED, elle, est calculée une seule fois au moment de l'écriture puis stockée dans le fichier de base de données : les lectures sont moins coûteuses, mais les écritures un peu plus. Les deux peuvent être indexées, mais STORED reste le bon choix quand l'expression est lourde ou quand la colonne est lue beaucoup plus souvent qu'elle n'est écrite.
Peut-on indexer une colonne générée en SQLite ?
Oui. CREATE INDEX fonctionne sur les colonnes générées, qu'elles soient VIRTUAL ou STORED. C'est d'ailleurs la principale raison de s'en servir : vous pouvez indexer une valeur dérivée (comme lower(email) ou un champ JSON extrait avec ->>) et laisser le planificateur de requêtes utiliser cet index sans avoir à réécrire chaque requête.
Peut-on ajouter une colonne générée avec ALTER TABLE ?
Oui, mais uniquement pour les colonnes VIRTUAL. La syntaxe ALTER TABLE ... ADD COLUMN ... GENERATED ALWAYS AS (...) VIRTUAL passe sans problème. En revanche, ajouter une colonne générée STORED via ALTER TABLE n'est pas pris en charge — il faudrait reconstruire la table. Mieux vaut donc anticiper si vous voulez des colonnes stockées sur des tables existantes.