Menu

Subquery in SQLite: SELECT annidate in WHERE, FROM e SELECT

Come annidare una SELECT dentro un'altra in SQLite: subquery scalari, IN/EXISTS, tabelle derivate, subquery correlate e quando una JOIN si legge meglio.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Una subquery è una SELECT dentro una SELECT

Una subquery è esattamente quello che sembra: un'istruzione SELECT infilata dentro un'altra istruzione, racchiusa tra parentesi. SQLite esegue la query interna, prende il risultato e lo passa a quella esterna.

Prepariamo un piccolo esempio da riutilizzare:

Cinque ordini, quattro clienti, due dei quali non hanno ordinato niente. Lo useremo per tutta la pagina.

Subquery nella WHERE: filtrare in base a una lista

La forma più comune: ricavi una lista di id con una query interna, poi filtri la query esterna in base a quella lista.

La query interna produce ogni customer_id che compare in orders. La query esterna tiene solo i clienti il cui id è in quella lista. Compaiono Cleo, Boris e Ada; Dmitri (nessun ordine) no.

IN (SELECT ...) è lo schema di base per "righe di A che hanno una corrispondenza in B". Leggilo mentalmente come "dove il valore di questa colonna è uno dei valori restituiti dalla query interna".

NOT IN: attenzione ai NULL

La domanda opposta, "quali clienti non hanno ordinato?", è a una riga di distanza:

Qui funziona. Ma NOT IN ha uno spigolo tagliente: se la subquery restituisce anche un solo NULL, l'intero NOT IN diventa NULL (che non è TRUE) e ottieni zero righe. Sorprendente e silenzioso.

L'abitudine sicura quando usi NOT IN su una colonna che potrebbe contenere NULL:

Oppure usa NOT EXISTS, che questo problema non ce l'ha proprio. Ci arriviamo tra poco.

Subquery scalari: una riga, una colonna

Una subquery scalare restituisce un singolo valore (una riga, una colonna) e puoi usarla ovunque sia atteso un valore.

La SELECT MAX(total) FROM orders interna restituisce 200. La query esterna filtra poi gli ordini che corrispondono a quel valore. Utile ogni volta che devi confrontare con un'aggregazione.

Puoi usare una subquery scalare anche nell'elenco della SELECT per aggiungere un valore calcolato a ogni riga:

Per ogni riga di customers la query interna viene eseguita una volta, con customers.id inserito al suo posto. È una subquery correlata: ne parliamo più sotto. Per i casi "un numero per riga" come questo, una LEFT JOIN con GROUP BY di solito è più veloce, ma la forma scalare si legge benissimo.

EXISTS: controllare solo se c'è una corrispondenza

EXISTS è il cugino più discreto di IN. Non gli interessano i valori: controlla solo se la subquery restituisce almeno una riga. Di solito dentro si scrive SELECT 1, perché la colonna non conta.

Questa query trova i clienti che hanno fatto almeno un ordine sopra 100. La query interna fa riferimento a c.id della query esterna: è questo che la rende correlata. SQLite smette di scorrere la tabella interna appena trova una corrispondenza, ed è per questo che EXISTS spesso batte IN per le domande del tipo "questa riga ha una riga collegata?".

La negazione, NOT EXISTS, è il modo sicuro rispetto ai NULL per chiedere "nessuna riga collegata":

Subquery nel FROM: una tabella derivata

Una subquery può stare ovunque possa stare una tabella, compresa la clausola FROM. La query interna diventa una "tabella derivata" temporanea e con un nome, su cui puoi fare join, filtri o aggregazioni.

La query interna calcola un totale per cliente. Quella esterna fa la media di quei totali per paese. Le aggregazioni in due fasi come questa sono proprio il motivo per cui esistono le tabelle derivate: quando non riesci a fare tutto con un solo GROUP BY.

L'alias AS per_customer è obbligatorio: ogni tabella derivata deve avere un nome.

Subquery correlate: eseguite per ogni riga esterna

Una subquery è correlata quando fa riferimento a una colonna della query esterna. SQLite deve rivalutare la query interna per ogni riga esterna: è flessibile, ma può diventare costoso.

Per ogni cliente, trova il suo ordine più grande. La query interna dipende da customers.id, quindi viene eseguita una volta per cliente. I clienti senza ordini ottengono NULL, che è proprio quello che vorresti.

Le subquery correlate sono la scelta naturale per "per ogni riga di A, calcola qualcosa da B". Se la tabella è piccola o la ricerca usa un indice, vanno bene. Su tabelle grandi senza indici di supporto, misura le prestazioni prima di andare in produzione: una JOIN con GROUP BY spesso è più veloce.

Subquery o JOIN: quale scegliere?

Queste due query rispondono alla stessa domanda:

Entrambe restituiscono le stesse righe. L'ottimizzatore di SQLite riscrive spesso una forma nell'altra internamente. Scegli in base alla leggibilità:

  • Usa una subquery quando devi solo filtrare e non vuoi che le colonne della tabella interna finiscano nel risultato.
  • Usa una JOIN quando il risultato ha bisogno di colonne di entrambe le tabelle.
  • Usa EXISTS quando chiedi "esiste almeno una riga collegata?": è più chiaro ed evita le trappole dei NULL di IN/NOT IN.

Nel dubbio, scrivi la versione che si spiega da sola quando la leggi ad alta voce.

Un errore comune: subquery che restituiscono più righe

Una subquery usata con = deve restituire al massimo una riga. Se ne restituisce di più, SQLite ne sceglie una (di fatto a caso) e ottieni risultati sbagliati in silenzio, senza nessun errore.

Usa IN quando la query interna potrebbe restituire più righe:

Se ti aspetti esattamente una riga e vuoi garantirlo, aggiungi LIMIT 1 e un ORDER BY, così almeno la scelta è deterministica. Meglio ancora: scrivi la query in modo che una sola riga sia garantita dai dati (filtra su una colonna univoca).

Prossimo passo: Common Table Expressions

Le subquery nel FROM diventano scomode in fretta, soprattutto quando ti serve la stessa tabella derivata due volte o quando l'annidamento arriva a tre livelli. Le Common Table Expressions (WITH ... AS (...)) ti permettono di dare un nome a una subquery all'inizio e di richiamarla per nome nel resto dell'istruzione. È la prossima pagina.

Domande frequenti

Cos'è una subquery in SQLite?

Una subquery è un'istruzione SELECT annidata dentro un'altra istruzione, racchiusa tra parentesi. SQLite esegue la query interna e passa il suo risultato a quella esterna. Le subquery possono comparire in WHERE, FROM, SELECT e in diverse altre clausole.

Che differenza c'è tra IN ed EXISTS in SQLite?

IN (SELECT ...) controlla se un valore corrisponde a una qualsiasi delle righe restituite dalla subquery. EXISTS (SELECT ...) controlla solo se la subquery produce almeno una riga: i valori non gli interessano. EXISTS di solito è la scelta migliore quando la query interna fa riferimento alla riga esterna (una subquery correlata).

In SQLite conviene usare una subquery o una JOIN?

Usa una JOIN quando nel risultato ti servono colonne di entrambe le tabelle. Usa una subquery quando devi solo filtrare o calcolare un singolo valore. L'ottimizzatore di SQLite spesso riscrive comunque una forma nell'altra, quindi scegli quella che si legge meglio.

Cos'è una subquery correlata in SQLite?

Una subquery correlata fa riferimento a una colonna della query esterna, quindi va rivalutata per ogni riga esterna. È flessibile ma può essere lenta sulle tabelle grandi. Se una subquery correlata diventa un collo di bottiglia, riscriverla come JOIN o come CTE spesso aiuta.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA