20th Jul 2022 Tempo di lettura: 11 minuti Subquery vs. CTE: un primo SQL Kamil Bladoszewski sql imparare sql sottoquery Indice Che cos'è una subquery? Che cos'è la CTE? Nessuna differenza...? Subquery vs CTE: qual è la differenza? Differenza n. 1: le CTE possono essere ricorsive Differenza n. 2: le CTE sono riutilizzabili Differenza n. 3: le CTE possono essere più leggibili Ma le subquery sono talvolta insostituibili Filtrare con una subquery Le subquery possono agire come colonne Subquery correlate Un'altra differenza: Le CTE devono essere nominate Subquery e CTE: cosa è meglio? Vi siete mai chiesti quali siano le differenze tra una subquery e una common table expression (CTE) in SQL? I concetti sembrano molto simili, ma conoscerne le differenze e sapere quando usarle vi aiuterà a scrivere query efficienti e leggibili. Per prima cosa spiegheremo i concetti alla base delle subquery e delle CTE. Poi vedremo alcuni esempi e infine analizzeremo i pro e i contro di ciascuna tecnica. Che cos'è una subquery? Una subquery è una query all'interno di una query. Può essere utilizzata in diversi modi: nella clausola FROM, per filtrare o anche come colonna. Per usare una subquery, basta aggiungere delle parentesi e inserire la query al suo interno. Nei primi esempi, lavoreremo con i dati relativi ai risultati di una gara di salto in lungo. Abbiamo a disposizione due tabelle: participant - Memorizza informazioni sui partecipanti al concorso: id - Un ID univoco per ogni partecipante. first_name - Il nome del partecipante. last_name - Il cognome del partecipante. jump - Memorizza informazioni sui salti effettuati dai partecipanti: id - L'ID di ogni salto. participant_id - L'ID del partecipante che ha effettuato il salto. contest_id - L'ID della gara in cui è stato effettuato il salto. length - Lunghezza del salto, in centimetri. participant idfirst_namelast_name 1AmishaBrown 2JamaalSanford 3HibaCameron jump idparticipant_idcontest_idlength 111667 212745 313723 421736 522669 623508 731664 832502 933739 Dal momento che si conoscono i dati utilizzati, è possibile dare un'occhiata ai seguenti esempi di sottoquery: SELECT first_name, last_name, length FROM participant JOIN jump ON jump.participant_id = participant.id WHERE length > ( SELECT AVG(length) FROM jump ); Questa query mostra i partecipanti con salti più lunghi della lunghezza media del salto. Nella condizione WHERE, abbiamo utilizzato una sottoquery per ottenere la lunghezza media del salto. Poiché la subquery restituisce un solo valore, è possibile confrontare facilmente i dati delle colonne. Un altro esempio: SELECT MAX(avg_length) AS max_length FROM ( SELECT first_name, last_name, AVG(length) AS avg_length FROM participant JOIN jump ON jump.participant_id = participant.id GROUP BY first_name, last_name ) AS average_lengths; In questa query, mostriamo la lunghezza media del salto più grande di ogni partecipante. Per ottenere il risultato, dobbiamo innanzitutto calcolare la lunghezza media del salto per ogni partecipante. Per farlo, utilizziamo una sottoquery all'interno della clausola FROM. Quindi, utilizziamo semplicemente MAX() per restituire la lunghezza media maggiore. Questi sono solo due esempi di subquery. L'argomento è vasto, anche se i casi d'uso sono piuttosto semplici, e i concetti sono troppi per essere descritti in questo articolo. Una breve panoramica delle subquery è disponibile nell'articolo SQL Subqueries sul blog LearnSQL.it . Se siete interessati a lavorare con le subquery, consultate la parte 6 del corso SQL Basics (si chiama addirittura Subqueries). Potete anche guardare gli episodi della nostra serie We Learn SQL su Youtube. Molti di essi sono stati dedicati alle subquery SQL. Ricordatevi di iscrivervi al nostro canale. Che cos'è la CTE? Una Common Table Expression (in breve CTE ) è una query che viene creata prima di scrivere la query principale. Poi, possiamo semplicemente usarla come una tabella regolare all'interno del nostro codice. Guardate l'esempio seguente. Ancora una volta, stiamo utilizzando i dati della gara di salto in lungo: WITH average_lengths AS ( SELECT first_name, last_name, AVG(length) AS avg_length FROM participant JOIN jump ON jump.participant_id = participant.id GROUP BY first_name, last_name ) SELECT MAX(avg_length) AS max_length FROM average_lengths; Il risultato è esattamente lo stesso dell'esempio precedente con la subquery: la lunghezza media del salto più grande per ogni partecipante. Tuttavia, invece di scrivere la nostra query all'interno della clausola FROM, l'abbiamo inserita nella clausola WITH (che precede la query principale). Abbiamo chiamato la nostra CTE average_length e l'abbiamo utilizzata nella clausola FROM della query principale. Naturalmente, le CTE possono essere molto più complicate di questo esempio. Ma non ne parleremo qui. Se si desidera approfondire la conoscenza delle CTE, si consiglia di consultare il corso Recursive Queries all'indirizzo LearnSQL.it. Nessuna differenza...? In un primo momento, si potrebbe pensare che non ci sia quasi nessuna differenza tra le subquery e le CTE. Abbiamo usato sia una subquery che una CTE nella clausola FROM e la sintassi era solo leggermente diversa. Tuttavia, non dimenticate il primo esempio: nella clausola WHERE abbiamo usato una subquery. Non si poteva usare una CTE, e questa non è l'unica differenza! Subquery vs CTE: qual è la differenza? Naturalmente, questo non significa che le CTE siano inferiori alle subquery. Esaminiamo le differenze tra le due, iniziando dalle CTE. Differenza n. 1: le CTE possono essere ricorsive Vediamo il primo vantaggio delle CTE. Le CTE consentono di utilizzare un concetto potente: la ricorsione. Grazie alla ricorsione, SQL è ora Turing completo: ogni programma che può essere scritto in qualsiasi linguaggio di programmazione può essere scritto anche in SQL. (Se avete dubbi sul fatto che SQL sia un linguaggio di programmazione, date un'occhiata a Is SQL a programming language? sul blog LearnSQL.it ). Come funziona la ricorsione in SQL? Consente alla CTE di richiamare se stessa fino a quando non viene soddisfatta una determinata condizione finale. A ogni passaggio, la query si espande e modifica i dati in suo possesso. Vediamo un esempio. Lavoreremo con i seguenti dati relativi ad alcuni dipendenti di un'azienda fittizia. Sono memorizzati nella tabella employee che contiene le seguenti colonne: id - Un ID univoco per ogni dipendente. first_name - Il nome del dipendente. last_name - Il cognome del dipendente. manager_id - L'ID del manager del dipendente. employee idfirst_namelast_namemanager_id 1MaisyBloomNULL 2CaineFarrow1 3WaqarJarvis2 4Lacey-MaiRahman2 5MerrynFrench3 Ora, vorremmo mostrare la catena di gestione dall'amministratore delegato (una persona senza valore nella colonna manager_id ) a ciascun dipendente. La query seguente risolverà questo problema. Guardate: WITH RECURSIVE employee_chain AS ( SELECT id, first_name, last_name, first_name || ' ' || last_name AS chain FROM employee WHERE manager_id IS NULL UNION ALL SELECT employee.id, employee.first_name, employee.last_name, chain || '->' || employee.first_name || ' ' || employee.last_name FROM employee_chain JOIN employee ON employee.manager_id = employee_chain.id ) SELECT first_name, last_name, chain FROM employee_chain; Il risultato sarà simile a questo: first_namelast_namechain MaisyBloomMaisy Bloom CaineFarrowMaisy Bloom->Caine Farrow WaqarJarvisMaisy Bloom->Caine Farrow->Waqar Jarvis Lacey-MaiRahmanMaisy Bloom->Caine Farrow->Lacey-Mai Rahman MerrynFrenchMaisy Bloom->Caine Farrow->Waqar Jarvis->Merryn French Abbiamo scritto una query che può creare facilmente un'intera catena di relazioni. Si potrebbe pensare che ciò possa essere ottenuto con delle sottoquery, ma man mano che la catena di gestione diventa sempre più profonda, si dovrà scrivere sempre più codice. La quantità di codice da scrivere dipende dalla profondità della catena, che può essere controllata solo con una CTE ricorsiva. Come funziona questa query? Inizia eseguendo la prima parte (prima di UNION ALL) e seleziona un dipendente senza manager (cioè Maisy Bloom). Poi, la parte sottostante la UNION ALL seleziona i dipendenti gestiti direttamente da Maisy (Caine Farrow). Poiché la query chiama se stessa, esegue nuovamente la stessa parte e seleziona tutti i dipendenti gestiti da Caine (Waqar Jarvis e Lacey-Mai Rahman). Ripete questa operazione finché ha righe da unire. Dopo aver attraversato l'intera catena di gestione, la query si ferma. Se questo è il primo incontro con la ricorsione in SQL, potrebbe essere un po' difficile da capire. È del tutto normale. Consultate la sezione Fallo in SQL: L'attraversamento ricorsivo di alberi in SQL per una spiegazione più dettagliata. Differenza n. 2: le CTE sono riutilizzabili Un enorme vantaggio delle CTE è che possono essere utilizzate più volte in una query. Non è necessario copiare l'intero codice CTE: è sufficiente inserire il nome della CTE. Utilizzando i dati della sezione precedente, vorremmo 1) filtrare i dipendenti che non hanno un manager e poi 2) mostrare ogni dipendente con il suo manager, ma solo se ha un manager. Il risultato sarà simile a questo: first_namelast_namefirst_namelast_name WaqarJarvisCaineFarrow Lacey-MaiRahmanCaineFarrow MerrynFrenchWaqarJarvis Vediamo come una CTE potrebbe risolvere questo compito: WITH not_null_manager AS ( SELECT * FROM employee WHERE manager_id IS NOT NULL ) SELECT nnm1.first_name, nnm1.last_name, nnm2.first_name, nnm2.last_name FROM not_null_manager AS nnm1 JOIN not_null_manager AS nnm2 ON nnm1.manager_id = nnm2.id; Vediamo ora come una subquery otterrebbe lo stesso risultato: SELECT nnm1.first_name, nnm1.last_name, nnm2.first_name, nnm2.last_name FROM ( SELECT * FROM employee WHERE manager_id IS NOT NULL ) AS nnm1 JOIN ( SELECT * FROM employee WHERE manager_id IS NOT NULL ) AS nnm2 ON nnm1.manager_id = nnm2.id; Come si può notare, la query CTE contiene meno codice. È anche più leggibile: si ripete semplicemente il nome della CTE (not_null_manager) invece di un intero pezzo di codice. Non c'è molta differenza nell'efficienza delle prestazioni tra queste due query. Anche se la CTE viene dichiarata una sola volta, il tempo di esecuzione è quasi lo stesso. Differenza n. 3: le CTE possono essere più leggibili Quindi, sappiamo che è possibile scrivere meno codice utilizzando le CTE. Ma che dire dell'organizzazione del codice? Ecco un altro esempio che si concentra sulla clausola FROM. Ricordate ancora i primi esempi? Quelli in cui si restituiva la lunghezza media del salto più grande? Se no, ecco un rapido ripasso. Questo utilizza una sottoquery: SELECT MAX(avg_length) AS max_length FROM ( SELECT first_name, last_name, AVG(length) AS avg_length FROM participant JOIN jump ON jump.participant_id = participant.id GROUP BY first_name, last_name ) AS average_lengths; E questo utilizza una CTE: WITH average_lengths AS ( SELECT first_name, last_name, AVG(length) AS avg_length FROM participant JOIN jump ON jump.participant_id = participant.id GROUP BY first_name, last_name ) SELECT MAX(avg_length) AS max_length FROM average_lengths; Quale pensate sia più leggibile? Direi che la seconda (CTE) è un po' meglio. Il codice è meno annidato e c'è un piccolo livello di astrazione. Si capisce subito che questa query seleziona la lunghezza media massima. La leggibilità delle CTE è ancora più evidente negli esempi della sezione precedente. Quando le query diventano sempre più grandi, l'uso delle CTE aumenta notevolmente la leggibilità del codice. E la leggibilità è fondamentale per uno sviluppo efficiente del codice. Se volete saperne di più sull'uso delle CTE per migliorare la leggibilità del codice, date un'occhiata all'articolo Come organizzare le query SQL con le CTE. Ma le subquery sono talvolta insostituibili Finora avete appreso alcune differenze tra le subquery e le CTE. A dire il vero, stavo cercando di convincervi che le CTE sono molto meglio delle subquery. Ma in questa sezione imparerete perché le subquery possono essere preziose. Filtrare con una subquery Il primo esempio di questo articolo ha utilizzato una subquery nella clausola WHERE. Non ho mostrato un esempio simile nella sezione CTE. Questo perché solo le subquery possono essere utilizzate nella clausola WHERE! Inoltre, ci sono molte parole chiave che si possono usare nella condizione WHERE, ad esempio ALL, ANY, EXISTS, e altre ancora! Purtroppo non posso spiegarle in questa sede, perché richiederebbe troppo tempo. Si consiglia invece di consultare la sezione Subqueries del nostro corso SQL Basics del nostro corso. Non solo imparerete a conoscere queste parole chiave, ma risolverete anche alcuni problemi che le utilizzano! Oppure, se volete una breve spiegazione, consultate l'articolo SQL Subqueries nel nostro blog. Le subquery possono agire come colonne È possibile utilizzare le subquery come se fossero colonne. L'unico vincolo è che la subquery deve restituire un solo valore. Guardate: SELECT DISTINCT contest_id, ( SELECT COUNT(length) FROM jump AS inside_jump WHERE inside_jump.contest_id = outside_jump.contest_id AND inside_jump.length > 600 ) AS longer_jumps, ( SELECT COUNT(length) FROM jump AS inside_jump WHERE inside_jump.contest_id = outside_jump.contest_id AND inside_jump.length <= 600 ) AS shorter_jumps FROM jump AS outside_jump; Per ogni gara, questa query restituisce il numero di salti superiori a 600 cm (calcolati nella prima sottoquery) e il numero di salti inferiori o uguali a 600 cm (calcolati nella seconda sottoquery). Il risultato sarà simile a questo: contest_idlonger_jumpsshorter_jumps 130 221 321 Subquery correlate Guardate di nuovo l'esempio precedente. Avete notato che ho usato un riferimento a una tabella nella query esterna all'interno della subquery? Ho persino fatto riferimento al valore della riga corrente di quella tabella. Questa si chiama "subquery correlata". Consente di utilizzare i valori della query esterna all'interno della subquery. È una tecnica molto utile, ma anche piuttosto complicata; non la spiegheremo in questo articolo. Tuttavia, vi invitiamo a consultare Correlated Subquery in SQL: A Beginner's Guide nel nostro blog per una spiegazione. Un'altra differenza: Le CTE devono essere nominate L'ultima differenza tra le CTE e le subquery sta nella denominazione. Le CTE devono sempre avere un nome. Nella maggior parte dei motori di database, invece, le subquery non richiedono alcun nome (l'unica eccezione è la clausola FROM nel mio motore di database preferito, PostgreSQL). È buona norma assegnare un nome alle subquery inserite nelle clausole FROM o SELECT, ma non è un obbligo. E, per essere precisi, non è possibile dare un nome alle subquery utilizzate nella clausola WHERE. Si potrebbe pensare che la denominazione non sia una grande differenza e che non influisca più di tanto. Tuttavia, può capitare di dover controllare rapidamente qualcosa nel database. In questo caso, la scelta potrebbe ricadere sulla sintassi più semplice. Anche se la query è meno leggibile, si tenga presente che tali query vengono raramente lette dopo essere state utilizzate. Subquery e CTE: cosa è meglio? Abbiamo imparato molto sulle differenze tra CTE e subquery. Allora, qual è la migliore? La risposta è: nessuna, nessuna delle due, oppure dipende: sia le subquery che le CTE hanno pro e contro. Ogni query dovrebbe essere analizzata e la scelta tra queste due dovrebbe essere fatta caso per caso. Ma per farlo, è necessario imparare a fondo entrambi i concetti. Per saperne di più sulle subquery, vi suggerisco la sezione Subqueries del corso LearnSQL. SQL Basics di LearnSQL. Se desiderate saperne di più sulle CTE, il corso Recursive Queries è la scelta migliore. Questi corsi vi aiuteranno ad apprendere rapidamente questi concetti. In questo modo, sarete in grado di decidere quali query beneficiano delle CTE e quali richiedono le subquery. Tuttavia, se avete già una certa dimestichezza con le subquery correlate e non volete imparare di nuovo le stesse cose, potreste anche voler affinare le vostre competenze con il nostro corso SQL Practice Set corso. Tags: sql imparare sql sottoquery