| name | sqlite-guide |
| description | Guide pour écrire des requêtes SQL et concevoir des schémas SQLite avec les bonnes pratiques. À utiliser quand l'utilisateur travaille avec SQLite, écrit des requêtes SQL ou conçoit des schémas de base de données. Se déclenche aussi avec "requête SQL", "schéma SQLite", "base de données SQLite", "migration SQL", "table SQLite", "query SQL". Also triggers on "SQLite schema", "SQLite query", "embedded database". |
Guide SQLite
Workflow
- Identifier le besoin — conception de schéma, écriture de requête, optimisation, migration ou debug.
- Examiner le contexte — schéma existant, version SQLite (
sqlite3 --version), volumes de données attendus.
- Choisir le pattern adapté — voir sections ci-dessous selon le cas.
- Livrer du code copiable — requêtes testables, schemas complets, commandes CLI directes.
- Signaler les pièges — typage dynamique, verrouillage de fichier, absence de types natifs date/bool.
Conception de schéma
Table de référence (template)
create table users (
id text not null primary key default ('u_' || lower(hex(randomblob(16)))),
created text not null default (strftime('%Y-%m-%dT%H:%M:%fZ')),
updated text not null default (strftime('%Y-%m-%dT%H:%M:%fZ')),
email text not null unique,
name text not null,
active integer not null default 1
) strict;
create trigger users_updated
after update on users
begin
update users set updated = strftime('%Y-%m-%dT%H:%M:%fZ')
where id = old.id;
end;
Conventions de nommage
| Élément | Convention |
|---|
| Clé primaire | id text + préfixe table (u_, p_, o_) + randomblob(16) |
| Timestamps | strftime('%Y-%m-%dT%H:%M:%fZ') — ISO 8601 avec ms, toujours UTC |
| Booléens | integer not null default 0 (0/1) — pas de type bool natif |
| JSON | text + contrainte check(json_valid(meta)) si nécessaire |
| Clés étrangères | user_id text not null references users(id) |
Activer les foreign keys (obligatoire à chaque connexion)
pragma foreign_keys = on;
SQLite n'active pas les FK par défaut. À mettre dans l'initialisation de chaque connexion.
Migrations
Toujours versionner avec une table dédiée :
create table if not exists migrations (
version integer primary key,
name text not null,
applied text not null default (strftime('%Y-%m-%dT%H:%M:%fZ'))
);
Pattern de migration safe :
begin;
alter table posts add column slug text;
insert into migrations(version, name) values (3, 'add_posts_slug');
commit;
Requêtes
Règles de base
- Écrire en minuscules — plus lisible, standard de facto.
- Toujours nommer les colonnes dans les
select de production — jamais select *.
- Préférer les CTEs (
with) aux sous-requêtes imbriquées profondes.
CTE — lecture claire
with active_users as (
select id, name, email
from users
where active = 1
),
recent_posts as (
select author_id, count(*) as cnt
from posts
where created > date('now', '-30 days')
group by author_id
)
select u.name, u.email, coalesce(rp.cnt, 0) as posts_last_30d
from active_users u
left join recent_posts rp on rp.author_id = u.id
order by posts_last_30d desc;
Upsert
insert into settings (key, value)
values ('theme', 'dark')
on conflict(key) do update set
value = excluded.value,
updated = strftime('%Y-%m-%dT%H:%M:%fZ');
Pagination (keyset > offset pour les grands volumes)
select id, title, created from posts
order by created desc
limit 20 offset 40;
select id, title, created from posts
where created < '2026-06-01T12:00:00.000Z'
order by created desc
limit 20;
Recherche — FTS5
create virtual table posts_fts using fts5(
title, body, content=posts, content_rowid=id
);
insert into posts_fts(posts_fts) values('rebuild');
select p.id, p.title, rank
from posts_fts
join posts p on p.id = posts_fts.rowid
where posts_fts match 'sqlite AND performance'
order by rank;
JSON (SQLite ≥ 3.38)
select json_extract(meta, '$.country') as country from users;
select * from orders
where json_extract(payload, '$.status') = 'paid';
select json_object('id', id, 'name', name) from users;
Optimisation & index
Analyser une requête
explain query plan
select * from posts where author_id = 'u_abc' order by created desc;
Créer les bons index
create index idx_posts_author on posts(author_id);
create index idx_posts_author_created on posts(author_id, created desc);
create index idx_active_users on users(email) where active = 1;
WAL mode (performances en écriture concurrente)
pragma journal_mode = wal;
pragma synchronous = normal;
Statistiques
pragma table_info(posts);
pragma index_list(posts);
analyze;
CLI SQLite — commandes utiles
sqlite3 mydb.db
sqlite3 mydb.db -cmd ".mode csv" ".import data.csv my_table"
sqlite3 -header -csv mydb.db "select * from users;" > users.csv
sqlite3 mydb.db .dump > backup.sql
sqlite3 mydb.db < schema.sql
Garde-fous & anti-patterns
| Anti-pattern | Problème | Solution |
|---|
select * en production | Casse si colonnes ajoutées, lisibilité zéro | Nommer explicitement les colonnes |
Table sans strict | SQLite accepte n'importe quel type — bugs silencieux | Toujours strict |
offset sur grande table | O(n) — ralentit proportionnellement | Keyset pagination |
like '%terme%' sur gros volumes | Full scan | FTS5 |
FK sans pragma foreign_keys = on | Contraintes ignorées silencieusement | Activer à chaque connexion |
Timestamps avec datetime() | Moins précis que strftime (pas de ms) | strftime('%Y-%m-%dT%H:%M:%fZ') |
| Connexions multiples en écriture sans WAL | Contention — SQLITE_BUSY fréquents | pragma journal_mode = wal |
| Migrations manuelles non versionnées | Impossible de rejouer ou auditer | Table migrations + transactions |
Booléens en text ('true'/'false') | Comparaisons cassées | integer 0/1 |
Critères de décision rapides
- SQLite ou autre SGBD ? — SQLite est idéal pour : applications embarquées, mobile, desktop, prototypes, fichiers de données locaux, tests. Passer à Postgres/MySQL si : accès concurrent massif en écriture, réseau multi-clients, réplication, extensions spécialisées.
- FTS5 ou
like ? — Dès que la table dépasse ~10k lignes ou que la recherche est fonctionnelle, FTS5.
- WAL ou DELETE mode ? — WAL par défaut pour toute app avec lectures et écritures simultanées.
- Index composite ou multiple ? — Composite si la requête filtre sur plusieurs colonnes ensemble (respecter l'ordre de la clause
where).