Menu

Sous-requêtes SQLite : SELECT imbriqués (WHERE, FROM)

Apprenez à imbriquer un SELECT dans un autre en SQLite : sous-requêtes scalaires, IN/EXISTS, tables dérivées, sous-requêtes corrélées, et quand préférer une JOIN.

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

Une sous-requête, c'est un SELECT dans un SELECT

Une sous-requête SQLite, c'est exactement ce que le nom indique : une instruction SELECT glissée à l'intérieur d'une autre, entre parenthèses. SQLite exécute d'abord la requête interne, récupère son résultat, puis le transmet à la requête externe.

Préparons un petit exemple qu'on va réutiliser tout au long :

Cinq commandes, quatre clients, dont deux n'ont jamais rien commandé. On va s'appuyer sur ce jeu de données tout au long de l'article.

Sous-requête SQLite dans WHERE : filtrer à partir d'une liste

C'est le cas le plus courant : on récupère une liste d'identifiants via une sous-requête, puis on filtre la requête principale avec cette liste.

La requête interne renvoie tous les customer_id présents dans orders. La requête externe ne conserve que les clients dont l'id figure dans cette liste. Cleo, Boris et Ada apparaissent ; Dmitri (aucune commande) est exclu.

IN (SELECT ...) est le motif de référence pour répondre à « quelles lignes de A ont une correspondance dans B ? ». Lisez-le mentalement comme : « là où la valeur de cette colonne fait partie des valeurs renvoyées par la sous-requête ».

NOT IN dans SQLite : attention aux NULL

La question inverse — « quels clients n'ont jamais commandé ? » — tient en une ligne :

Ça marche très bien ici. Mais NOT IN cache un piège vicieux : si la sous-requête renvoie ne serait-ce qu'un seul NULL, l'expression NOT IN complète devient NULL (donc pas TRUE), et vous vous retrouves avec zéro ligne. Surprenant, et surtout sans le moindre message d'erreur.

Le réflexe à adopter quand on utilise NOT IN sur une colonne susceptible de contenir des NULL :

Ou alors, utilise plutôt NOT EXISTS, qui n'a tout simplement pas ce souci. On y reviendra.

Sous-requête scalaire SQLite : une ligne, une colonne

Une sous-requête scalaire renvoie une valeur unique — une seule ligne, une seule colonne — et vous pouvez la glisser partout où une valeur est attendue.

La sous-requête interne SELECT MAX(total) FROM orders renvoie 200. La requête externe filtre ensuite les commandes correspondant à cette valeur. Pratique dès qu'il faut comparer une ligne à un agrégat.

On peut aussi placer une sous-requête scalaire dans la clause SELECT pour rattacher une valeur calculée à chaque ligne :

Pour chaque ligne de customers, la requête interne s'exécute une fois en injectant customers.id. C'est ce qu'on appelle une sous-requête corrélée — on y revient plus bas. Dans ce genre de cas où l'on veut « un nombre par ligne », un LEFT JOIN couplé à un GROUP BY sera généralement plus performant, mais la forme scalaire a quelque chose de très élégant à la lecture.

EXISTS SQLite : vérifier simplement si une ligne correspond

EXISTS est le cousin plus discret de IN. Peu importe les valeurs renvoyées : il regarde uniquement si la sous-requête retourne au moins une ligne. On écrit d'ailleurs souvent SELECT 1 à l'intérieur, puisque la colonne n'a aucune importance.

Cette requête récupère les clients ayant passé au moins une commande de plus de 100. La sous-requête interne fait référence à c.id issu de la requête englobante : c'est précisément ce qui la rend corrélée. SQLite arrête de parcourir la table interne dès qu'il trouve une correspondance, et c'est pour ça qu'EXISTS est souvent plus rapide qu'IN quand on cherche à savoir si « cette ligne a-t-elle une ligne liée ? ».

Sa négation, NOT EXISTS, est la façon sûre vis-à-vis des NULL de poser la question inverse : « pas de ligne liée » :

Sous-requête dans FROM : la table dérivée

Une sous-requête SQLite peut apparaître partout où une table est attendue — y compris dans la clause FROM. La requête interne se transforme alors en une « table dérivée » temporaire et nommée, sur laquelle on peut faire des jointures, appliquer des filtres ou lancer des agrégations.

La requête interne calcule un total par client. La requête externe, elle, fait la moyenne de ces totaux par pays. Ce genre d'agrégation en deux temps, c'est exactement à ça que servent les tables dérivées — typiquement quand un seul GROUP BY ne suffit pas.

L'alias AS per_customer est obligatoire : toute table dérivée doit avoir un nom.

Sous-requête corrélée SQLite : exécution ligne par ligne

Une sous-requête est dite corrélée lorsqu'elle référence une colonne de la requête englobante. SQLite doit alors réévaluer la sous-requête pour chaque ligne de la requête externe : c'est souple, mais ça peut vite coûter cher en performances.

Pour chaque client, on cherche sa commande la plus élevée. La requête interne dépend de customers.id, donc elle s'exécute une fois par client. Les clients sans commande obtiennent NULL — ce qui correspond exactement au comportement attendu.

La sous-requête corrélée SQLite est l'outil idéal pour ce genre de scénario : « pour chaque ligne de A, calculer quelque chose à partir de B ». Si la table est petite ou si la recherche s'appuie sur un index, pas de souci. En revanche, sur de grosses tables sans index adapté, mesurez avant de mettre en production — un JOIN avec GROUP BY se révèle souvent plus rapide.

Sous-requête ou JOIN : que choisir ?

Les deux requêtes ci-dessous répondent à la même question :

Les deux requêtes renvoient exactement les mêmes lignes. En interne, l'optimiseur SQLite réécrit d'ailleurs souvent l'une en l'autre. Le choix se fait donc sur un critère de lisibilité :

  • Privilégiez une sous-requête SQLite quand il s'agit uniquement de filtrer, sans polluer le résultat avec des colonnes de la table interne.
  • Préférez un JOIN quand le résultat doit contenir des colonnes des deux tables.
  • Utilisez EXISTS dès que la question posée est « existe-t-il au moins une ligne liée ? » — c'est plus clair et ça évite les pièges des NULL qu'on rencontre avec IN/NOT IN.

Dans le doute, écrivez la version qui s'explique d'elle-même quand on la lit à voix haute.

Un piège classique : les sous-requêtes qui renvoient plusieurs lignes

Une sous-requête utilisée avec = doit renvoyer au plus une ligne. Si elle en renvoie plusieurs, SQLite en choisit une (en pratique au hasard) et vous obtenez un résultat silencieusement faux — sans la moindre erreur.

Utilisez IN lorsque la sous-requête peut renvoyer plusieurs lignes :

Si vous attendez vraiment une seule ligne et que vous voulez forcer ce comportement, ajoutez LIMIT 1 accompagné d'un ORDER BY — au moins le choix devient déterministe. Mieux encore : structurez la requête de façon à ce qu'une seule ligne soit garantie par les données elles-mêmes (en filtrant sur une colonne unique, par exemple).

La suite : les Common Table Expressions

Les sous-requêtes dans FROM deviennent vite ingérables, surtout quand il faut réutiliser la même table dérivée deux fois, ou quand l'imbrication atteint trois niveaux. Les Common Table Expressions (WITH ... AS (...)) permettent de nommer une sous-requête en amont, puis de la réutiliser par son nom dans le reste de la requête. C'est le sujet de la page suivante.

Questions fréquentes

Qu'est-ce qu'une sous-requête en SQLite ?

Une sous-requête est un SELECT imbriqué à l'intérieur d'une autre instruction, entouré de parenthèses. SQLite exécute la requête interne d'abord, puis transmet son résultat à la requête externe. On peut placer une sous-requête dans WHERE, FROM, SELECT et plusieurs autres clauses.

Quelle différence entre IN et EXISTS en SQLite ?

IN (SELECT ...) vérifie qu'une valeur correspond à au moins une ligne renvoyée par la sous-requête. EXISTS (SELECT ...) se contente de tester si la sous-requête retourne au moins une ligne — peu importe les valeurs. En général, EXISTS est plus adapté quand la requête interne fait référence à une colonne de la requête externe (sous-requête corrélée).

Sous-requête ou JOIN : que choisir en SQLite ?

Utilisez une JOIN quand vous avez besoin de colonnes des deux tables dans le résultat final. Une sous-requête suffit quand vous voulez juste filtrer ou calculer une valeur unique. De toute façon, l'optimiseur de SQLite réécrit souvent l'une en l'autre — autant choisir la forme la plus lisible.

Qu'est-ce qu'une sous-requête corrélée en SQLite ?

Une sous-requête corrélée fait référence à une colonne de la requête externe, ce qui force SQLite à la réévaluer pour chaque ligne. C'est très flexible, mais ça peut vite devenir coûteux sur de grosses tables. Si une sous-requête corrélée se révèle être un goulot d'étranglement, la réécrire avec une JOIN ou une CTE règle souvent le problème.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER