21st Jul 2022 Tempo di lettura: 10 minuti 5 motivi per cui dovreste usare le CTE invece delle subquery Kateryna Koidan sql imparare sql CTE subqueries Indice Espressioni di tabella comuni e subquery 5 motivi per scegliere le CTE 5 motivi per usare le CTE piuttosto che le subquery #1. Le CTE usano nomi significativi #2. Le CTE sono riutilizzabili all'interno di una query #3. Le CTE dividono in parti i calcoli complessi #4. Le CTE annidate sono davvero interessanti #5. Le CTE consentono la ricorsione Esercitiamoci con le CTE! Le espressioni di tabella comuni, o CTE, sono state introdotte in SQL:1999 per gestire i casi in cui il risultato di una query viene utilizzato all'interno di un'altra query. Ma non esistevano già le subquery per questo? In questo articolo dimostrerò con diversi esempi perché le CTE sono migliori delle subquery per la struttura e la leggibilità delle query SQL. Iniziamo ricordando cosa sono le CTE e le subquery e come si differenziano. Espressioni di tabella comuni e subquery Una subquery è una query annidata all'interno di un'altra query. Può essere inserita in qualsiasi punto di una query, anche all'interno di un'altra subquery. La sintassi è molto semplice: basta mettere la subquery tra parentesi e inserirla nella query principale, ovunque sia necessaria. Si noti che le sottoquery vengono eseguite prima della query principale (detta anche query padre o query esterna), in modo che i loro risultati possano essere utilizzati dalla query principale. Spesso è piuttosto complicato leggere una query SQL con più subquery perché, anche se vengono eseguite per prime, sono definite da qualche parte nella query principale. Per saperne di più sulle subquery SQL, consultare questa guida introduttiva. Una Common Table Expression (CTE), detta anche clausola WITH, è un insieme di risultati temporaneo con nome che può essere citato in qualsiasi punto della query. A differenza delle subquery, che vengono inserite esattamente nel punto in cui servono, tutte le CTE vengono definite prima della query principale e vengono poi citate nella query utilizzando il nome assegnato. Negli esempi che seguono, vedremo come questo migliora significativamente la struttura e la leggibilità di una query SQL. Si definiscono prima tutti gli insiemi di risultati temporanei di cui si ha bisogno e poi li si usa nella query principale quando è necessario. Un'introduzione dettagliata alle espressioni di tabella comuni è disponibile qui. Per saperne di più sulla differenza tra le CTE e le subquery SQL, consultate questo articolo. 5 motivi per scegliere le CTE Chi studia l'SQL si chiede spesso se sia meglio usare un'espressione di tabella comune o una subquery quando si scrive una query SQL complessa. Vediamo alcuni esempi per spiegare perché si dovrebbe preferire una CTE a una subquery. Per questi esempi, utilizzerò la seguente tabella che riassume le prestazioni dei dipendenti di diversi dipartimenti. performance idnamedepartmentyears_experiencekpi 11Marta StewartSales168.8 12Cathrine BlackSales191.7 13Julian LeeOperations695.5 14Oliver HenriksonSales578.1 15Julia GreySales473.2 16Penelope GreenOperations382.7 17William SmithSales297.4 18Gabriel GarciaOperations190.1 19Nikita MooreOperations175.2 20Lucy MillerFinance379.9 21Sheldon CooperFinance398.9 22Leonard HofstadterFinance293.4 Per ogni dipendente, abbiamo l'ID, il nome completo, il reparto, gli anni di esperienza e un punteggio di completamento KPI su una scala da 0 a 100. Vediamo come le CTE e le subquery possono aiutarci ad analizzare questi dati. 5 motivi per usare le CTE piuttosto che le subquery #1. Le CTE usano nomi significativi È possibile assegnare alle CTE nomi significativi che specificano l'intenzione e rendono la query più leggibile. Per esempio, supponiamo di voler confrontare le prestazioni di ciascun dipendente con la media dei KPI completati nei rispettivi reparti. Per aggiungere la colonna corrispondente alla nostra tabella, dobbiamo prima calcolare il KPI medio per ogni reparto. La nostra intera query con una sottoquery è la seguente: SELECT p.name, p.department, p.kpi, avg.average_dep_kpi FROM performance p JOIN (SELECT department, AVG(kpi) AS average_dep_kpi FROM performance GROUP BY department) AS avg ON p.department = avg.department; namedepartmentkpiaverage_dep_kpi Marta StewartSales68.881.84 Cathrine BlackSales91.781.84 Julian LeeOperations95.585.875 Oliver HenriksonSales78.181.84 Julia GreySales73.281.84 Penelope GreenOperations82.785.875 William SmithSales97.481.84 Gabriel GarciaOperations90.185.875 Nikita MooreOperations75.285.875 Lucy MillerFinance79.990.733 Sheldon CooperFinance98.990.733 Leonard HofstadterFinance93.490.733 È possibile ottenere lo stesso risultato utilizzando un'espressione di tabella comune: WITH avg_department_kpi AS (SELECT department, AVG(kpi) AS average_dep_kpi FROM performance GROUP BY department) SELECT p.name, p.department, p.kpi, avg.average_dep_kpi FROM performance p JOIN avg_department_kpi avg ON p.department = avg.department; Le CTE e le subquery danno lo stesso risultato e le query sono più o meno della stessa lunghezza. Tuttavia, la leggibilità della query CTE è migliore. Il significato di una query complessa può essere ovvio per voi quando guardate il vostro codice, ma potrebbe non essere affatto chiaro per i vostri colleghi. Quando si legge la versione CTE: Si vede la query nell'ordine in cui verrà eseguita: prima la sottoquery e poi la query principale. È possibile determinare lo scopo della sottoquery in base al suo nome. Nel nostro caso, avg_department_kpi si riferisce alla CTE che produce la media dei KPI per ogni reparto. Allo stesso tempo, si può notare che la query CTE è un po' più lunga della versione subquery. Non è sempre così, soprattutto quando abbiamo bisogno della stessa sottoquery più volte nella nostra query principale. Se non conoscete la sintassi WITH, potete esercitarvi con le comuni espressioni di tabella nel nostro corso interattivo. Per ora, passiamo al secondo motivo per preferire le CTE alle subquery. #2. Le CTE sono riutilizzabili all'interno di una query A differenza delle subquery, non è necessario ripetere la definizione di una CTE ogni volta che se ne ha bisogno nella query. La si definisce una sola volta, all'inizio della query, e poi la si richiama quando è necessario. Supponiamo di voler confrontare le prestazioni di diversi reparti. In particolare, vogliamo vedere il KPI medio di ogni reparto e il KPI medio minimo e massimo tra i reparti. Potremmo affrontare questo problema utilizzando una query SQL con tre sottoquery: Trovare il KPI medio per ogni reparto. Trovare il KPI medio minimo tra i reparti. Trovare il KPI medio massimo tra i reparti. Si noti che le ultime due sottoquery avranno entrambe bisogno del risultato della prima. Pertanto, se si utilizzano le subquery, la nostra soluzione includerà subquery definite all'interno di altre subquery. Il tutto appare piuttosto confuso: SELECT ad.department, ad.average_kpi, min.min_avg_kpi_department, max.max_avg_kpi_department FROM (SELECT department, AVG(kpi) AS average_kpi FROM performance GROUP BY department) AS ad CROSS JOIN (SELECT MIN (average_kpi) AS min_avg_kpi_department FROM (SELECT department, AVG(kpi) AS average_kpi FROM performance GROUP BY department) AS ad) AS min CROSS JOIN (SELECT MAX (average_kpi) AS max_avg_kpi_department FROM (SELECT department, AVG(kpi) AS average_kpi FROM performance GROUP BY department) AS ad) AS max; Al contrario, quando si utilizzano le espressioni di tabella comuni, si definiscono le tre CTE all'inizio della query, si fa riferimento ad esse nella query principale quando necessario e si evitano le subquery multiple annidate: WITH avg_per_department AS ( SELECT department, AVG(kpi) AS average_kpi FROM performance GROUP BY department), min_kpi_department AS ( SELECT MIN (average_kpi) AS min_avg_kpi_department FROM avg_per_department), max_kpi_department AS ( SELECT MAX (average_kpi) AS max_avg_kpi_department FROM avg_per_department) SELECT ad.department, ad.average_kpi, min.min_avg_kpi_department, max.max_avg_kpi_department FROM avg_per_department ad CROSS JOIN min_kpi_department min CROSS JOIN max_kpi_department max; departmentaverage_kpimin_avg_kpi_departmentmax_avg_kpi_department Finance90.73381.8490.733 Operations85.87581.8490.733 Sales81.8481.8490.733 In questo caso, si può notare che le query WITH rendono il codice non solo più leggibile, ma anche molto più breve. #3. Le CTE dividono in parti i calcoli complessi Le CTE rendono più chiaro il processo di calcolo. Quando si usano le subquery per i calcoli, la query si trasforma spesso in un groviglio di subquery. Le CTE, invece, mostrano il processo di calcolo in modo più chiaro. Supponiamo di voler sapere quanti dipendenti junior ed esperti ci sono in ogni reparto. Considereremo junior i dipendenti con due o meno anni di esperienza e esperti quelli con più di due anni di esperienza. In pratica, abbiamo bisogno di due sottoquery: Calcolare il numero di dipendenti junior in ogni reparto. Calcolare il numero di dipendenti esperti in ogni reparto. Ecco come possiamo affrontare la questione con le sottoquery: SELECT e.department, e.experienced_employees, j.junior_employees FROM (SELECT department, COUNT(*) AS experienced_employees FROM performance WHERE years_experience > 2 GROUP BY department) AS e JOIN (SELECT department, COUNT(*) AS junior_employees FROM performance WHERE years_experience <= 2 GROUP BY department) AS j ON e.department = j.department; departmentexperienced_employeesjunior_employees Finance21 Operations22 Sales23 L'esempio non è molto complicato, quindi probabilmente sarete in grado di seguire la query di cui sopra. Tuttavia, si noti che: È necessario indovinare lo scopo di ogni sottoquery. Non è immediatamente chiaro quali tabelle siano unite. La clausola SELECT elenca i campi da visualizzare nell'output, ma questi campi vengono definiti solo successivamente all'interno delle sottoquery. Quando i calcoli diventano più complicati, è davvero difficile seguire il processo di calcolo attraverso tutte le sottoquery. Al contrario, il processo di calcolo con le CTE è molto più leggibile: WITH experienced AS (SELECT department, COUNT(*) AS experienced_employees FROM performance WHERE years_experience > 2 GROUP BY department), junior AS (SELECT department, COUNT(*) AS junior_employees FROM performance WHERE years_experience <= 2 GROUP BY department) SELECT e.department, e.experienced_employees, j.junior_employees FROM experienced e JOIN junior j ON e.department = j.department; In questo caso, si inizia definendo tutti gli insiemi temporanei di risultati necessari per i calcoli. Poi si scrive una semplice query per ottenere l'output necessario. Tutto è ordinato e chiaro. Credetemi, apprezzerete molto la struttura che le comuni espressioni di tabella forniscono quando lavorerete con calcoli più complessi. Per dimostrare il mio punto di vista, voglio proporvi un altro esempio con calcoli annidati. #4. Le CTE annidate sono davvero interessanti Considerando la riutilizzabilità delle CTE e la loro capacità di chiarire il processo di calcolo, non sorprende che le clausole WITH siano perfette per i calcoli annidati. Supponiamo di voler calcolare quanti dipendenti in ogni reparto hanno un punteggio KPI (1) superiore alla media o (2) inferiore alla media nel rispettivo reparto. In particolare, per ogni reparto, vogliamo mostrare il punteggio KPI medio, il numero di dipendenti con KPI superiore alla media e il numero di dipendenti con KPI inferiore alla media. In pratica, abbiamo bisogno di tre sottoquery: Ottenere il KPI medio per ogni reparto. Ottenere il numero di dipendenti che hanno un punteggio KPI superiore alla media. Per ottenere il numero di dipendenti che hanno un punteggio KPI inferiore alla media. Tuttavia, quando si utilizzano le subquery, è necessario aggiungere la prima subquery tre volte, di cui due volte quando sarà annidata all'interno delle altre due subquery. La query risultante appare piuttosto disordinata: SELECT avg.department, avg.average_kpi, aa.employees_above_average, ba.employees_below_average FROM (SELECT department, AVG(kpi) AS average_kpi FROM performance GROUP BY department) AS avg JOIN (SELECT p.department, count(*) AS employees_above_average FROM performance p JOIN (SELECT department, AVG(kpi) AS average_kpi FROM performance GROUP BY department) AS avg ON p.department = avg.department WHERE kpi > average_kpi GROUP BY p.department) AS aa ON avg.department = aa.department JOIN (SELECT p.department, count(*) AS employees_below_average FROM performance p JOIN (SELECT department, AVG(kpi) AS average_kpi FROM performance GROUP BY department) AS avg ON p.department = avg.department WHERE kpi < average_kpi GROUP BY p.department) AS ba ON avg.department = ba.department; departmentaverage_kpiemployees_above_averageemployees_below_average Finance90.73321 Operations85.87522 Sales81.8423 La query ha funzionato e abbiamo ottenuto l'output che volevamo, ma notate quanto sia difficile seguire le molteplici subquery annidate. Nel mondo reale, spesso le cose si complicano ulteriormente. Per contro, vediamo come appare ordinata la stessa query quando si utilizzano le CTE anziché le subquery: WITH avg_kpi_department AS (SELECT department, AVG(kpi) AS average_kpi FROM performance GROUP BY department), above_average AS (SELECT p.department, count(*) AS employees_above_average FROM performance p JOIN avg_kpi_department avg ON p.department = avg.department WHERE kpi > average_kpi GROUP BY p.department), below_average AS (SELECT p.department, count(*) AS employees_below_average FROM performance p JOIN avg_kpi_department avg ON p.department = avg.department WHERE kpi < average_kpi GROUP BY p.department) SELECT avg.department, avg.average_kpi, aa.employees_above_average, ba.employees_below_average FROM avg_kpi_department avg JOIN above_average aa ON avg.department = aa.department JOIN below_average ba ON avg.department = ba.department; È possibile seguire chiaramente come sono stati definiti i tre insiemi temporanei di risultati. Si può capire il loro scopo dai nomi assegnati. Non è necessario ripetere più volte la stessa sottoquery all'interno della stessa query principale: quando si arriva alla query principale, tutto il lavoro preparatorio è stato fatto e si può semplicemente unire le CTE già definite. #5. Le CTE consentono la ricorsione Infine, le CTE sono ottime per elaborare grafi, alberi e altre strutture gerarchiche. Questo perché la sintassi WITH è in grado di elaborare la ricorsione. Una query ricorsiva è una query che fa riferimento a se stessa. Ad esempio, se abbiamo una struttura organizzativa tipica, in cui ogni dipendente ha un superiore e i superiori hanno più subordinati, le CTE ricorsive possono aiutarci ad analizzare questi dati in modo efficiente. Con una sola query SQL e dati a livello individuale, possiamo calcolare lo stipendio totale per ogni reparto e sottodipartimento o il numero totale di giorni di ferie non utilizzati in ogni unità organizzativa. La sintassi delle CTE ricorsive è piuttosto complicata. Per una spiegazione dettagliata, vi consiglio di leggere questo articolo che spiega come le query ricorsive elaborano le strutture gerarchiche. Si consiglia di leggere anche questi esempi di applicazione delle query ricorsive alle strutture a grafo. Esercitiamoci con le CTE! Ora che vi siete resi conto dei vantaggi che le CTE apportano alle query SQL, è il momento di fare pratica! Vi consiglio di iniziare con il nostro Recursive Queries (114 sfide di codifica), che tratta tutti i tipi di espressioni di tabella comuni, comprese le CTE semplici, le CTE annidate e le CTE ricorsive. Maggiori dettagli in questo articolo riassuntivo. Grazie per aver letto e buon apprendimento! Tags: sql imparare sql CTE subqueries