Voir ce qui vient de se passer
Quand on exécute un INSERT, un UPDATE ou un DELETE, SQLite indique combien de lignes ont été touchées — mais pas lesquelles, ni à quoi ressemblent leurs valeurs finales. La parade classique consiste à enchaîner avec un SELECT. Résultat : deux allers-retours, deux requêtes, et une petite fenêtre de course pendant laquelle quelqu'un d'autre peut modifier la ligne entre les deux.
La clause RETURNING règle le problème. On la colle à la fin d'une requête d'écriture, on liste les colonnes que l'on veut récupérer, et SQLite renvoie les lignes concernées comme si on venait de faire un SELECT dessus :
Une seule requête, un seul aller-retour, et vous récupérez l'id généré ainsi que la valeur par défaut de created_at que la base a remplie pour vous.
La clause RETURNING est apparue avec SQLite 3.35.0 (mars 2021). Si votre requête échoue avec une erreur de syntaxe, vérifiez d'abord SELECT sqlite_version(); — les versions plus anciennes ne connaissent tout simplement pas ce mot-clé.
Récupérer l'ID après un INSERT en SQLite
La raison la plus fréquente d'utiliser RETURNING, c'est justement de récupérer la clé primaire auto-générée juste après un INSERT :
Avant l'arrivée de RETURNING, on faisait l'insertion puis on appelait last_insert_rowid() (ou l'équivalent fourni par le driver) sur la même connexion. Ça marche toujours, mais ça repose sur l'état de la connexion — un vrai piège dès qu'on touche à un pool ou à du multi-threading. À l'inverse, RETURNING id est explicite, rattaché à la requête elle-même, et se comporte de façon identique quel que soit le contexte qui héberge la connexion.
Si votre table ne déclare pas explicitement de INTEGER PRIMARY KEY, vous pouvez quand même récupérer l'identifiant de ligne implicite :
Toute table SQLite classique possède un rowid, et la clause RETURNING vous le restitue sans détour.
Plusieurs colonnes et expressions
La clause RETURNING accepte exactement la même syntaxe que la liste de colonnes d'un SELECT. Vous pouvez énumérer des colonnes, utiliser *, construire des expressions et leur attribuer des alias :
La clause RETURNING * est bien pratique quand vous voulez tout récupérer d'un coup — y compris les valeurs par défaut générées par la base — sans avoir à lister chaque colonne :
Vous voyez apparaître le nouvel id, le name que vous avez transmis et le timestamp calculé par SQLite.
RETURNING avec UPDATE
Sur un UPDATE, la clause RETURNING vous renvoie les valeurs après modification — autrement dit, la ligne telle qu'elle est une fois vos changements appliqués :
Vous récupérez le nouveau solde d'Ada, soit 125, et non l'ancien 100. C'est exactement pour ça que RETURNING est parfait pour les compteurs atomiques ou les opérations de crédit/débit : plus besoin de faire lecture → calcul → écriture → relecture.
Si la clause WHERE cible plusieurs lignes, vous obtenez une ligne en retour par ligne modifiée :
Trois lignes insérées, trois lignes renvoyées. L'ordre n'est pas garanti — si l'ordre vous importe, faites le tri côté client.
RETURNING avec DELETE
Sur un DELETE, la clause RETURNING vous renvoie les lignes telles qu'elles étaient juste avant leur suppression. Pratique pour archiver, tenir un journal d'audit, ou simplement vérifier ce qui a bien été supprimé :
Vous récupérez les deux sessions expirées avec tous leurs champs intacts, alors qu'elles n'existent plus dans la table. Pratique si vous voulez les déplacer ailleurs : c'est exactement le scénario idéal pour une table d'archive — vous lisez le résultat et vous l'insérez ailleurs dans la même transaction.
RETURNING avec un UPSERT
La clause RETURNING fonctionne aussi avec INSERT ... ON CONFLICT ... DO UPDATE. La ligne renvoyée correspond à la branche qui s'est exécutée — soit la nouvelle insertion, soit la mise à jour déclenchée par le conflit :
Lancez cette requête deux fois. Au premier passage, elle insère la ligne et renvoie ('visits', 1). Au second, le conflit se déclenche, la valeur est incrémentée et vous récupérez ('visits', 2). Dans les deux cas : une seule requête, une seule ligne en sortie — pas besoin de se demander « est-ce que ça a inséré ou mis à jour ? » avant de continuer.
C'est le pattern le plus propre en SQLite pour faire « donne-moi la valeur courante, et créez-la si elle n'existe pas » sans aller-retour.
Quelques pièges à connaître
Voici les détails qui surprennent souvent :
RETURNINGvoit toujours la ligne après modification pourINSERTetUPDATE, et avant modification pourDELETE. Aucune syntaxe ne permet d'obtenir l'autre version.- L'ordre des lignes renvoyées n'est pas garanti. Ajoutez un
ORDER BYcôté client si ça compte pour vous. - Vous ne pouvez pas mettre
RETURNINGdans une sous-requête. C'est une clause de premier niveau sur l'instruction d'écriture, pas une expression. RETURNINGne reflète pas les données modifiées par les triggersBEFORE— il renvoie les valeurs réellement écrites. Les triggersAFTERs'exécutent entre l'écriture et le retour de la ligne.- Les colonnes générées et les valeurs
DEFAULTapparaissent bien dans le résultat. C'est précisément pour ça queRETURNING *est un moyen rapide d'inspecter ce que la base a rempli pour vous.
La suite : importer des données CSV
RETURNING est parfait quand vous écrivez une ligne ou quelques-unes à la fois et que vous voulez voir le résultat immédiatement. Mais quand il s'agit de charger des milliers de lignes depuis un fichier, on passe plutôt aux outils d'import CSV de SQLite — c'est le sujet de la page suivante.
Questions fréquentes
Est-ce que SQLite prend en charge la clause RETURNING ?
Oui, depuis la version 3.35.0 (sortie en mars 2021). Vous pouvez ajouter RETURNING à un INSERT, un UPDATE ou un DELETE pour récupérer les lignes touchées. Sur une version plus ancienne, le parseur va refuser la requête — vérifiez avec SELECT sqlite_version();.
Comment récupérer l'ID d'une ligne qu'on vient d'insérer en SQLite ?
Utilisez INSERT ... RETURNING id (ou RETURNING rowid si la table n'a pas de clé primaire explicite). La valeur générée est renvoyée dans la même requête, donc plus besoin d'enchaîner avec un appel à last_insert_rowid().
Peut-on renvoyer plusieurs colonnes avec RETURNING ?
Oui. Listez les colonnes voulues séparées par des virgules, comme dans un SELECT : RETURNING id, name, created_at. Vous pouvez aussi utiliser RETURNING * pour tout récupérer, ou écrire des expressions du type RETURNING id, price * quantity AS total.
RETURNING fonctionne-t-elle avec UPSERT et ON CONFLICT ?
Oui. INSERT ... ON CONFLICT ... DO UPDATE ... RETURNING ... renvoie la ligne, qu'elle vienne d'être insérée ou mise à jour par la résolution de conflit. C'est clairement la façon la plus propre de faire un upsert et de lire le résultat en un seul aller-retour.