21st Sep 2023 Tempo di lettura: 12 minuti Cos'è un CTE in T-SQL? Una guida approfondita per i principianti con 7 esempi Ignacio L. Bisso sql cte T-SQL Indice Espressioni di tabella comuni (CTE) in T-SQL Esempio 1: una semplice CTE Esempio 2: Utilizzo di CTE con colonne rinominate (alias) in T-SQL Esempio 3: Utilizzo di più CTE nella stessa query T-SQL Esempio 4: Una query SQL Server con una CTE basata su un'altra CTE Esempio 5: Utilizzo di una CTE in una INSERZIONE T-SQL Esempio 6: Utilizzo di una CTE in un UPDATE di SQL Server Esempio 7: Utilizzo di CTE per Query ricorsive in T-SQL Le CTE sono una potente caratteristica del linguaggio T-SQL Una Common Table Expression (CTE) è una potente funzione T-SQL che semplifica la creazione di query in SQL Server. Le CTE funzionano come tabelle virtuali (con record e colonne) che vengono create al volo durante l'esecuzione di una query. Vengono consumate dalla query e distrutte dopo l'esecuzione della stessa. In alcuni casi, come quando la query si aspetta dati in un formato specifico e le tabelle di origine hanno i dati in un altro formato, una CTE può fare da ponte per trasformare i dati nelle tabelle di origine nel formato previsto dalla query. In questo articolo spiegheremo come creare una CTE in T-SQL, il dialetto SQL utilizzato da Microsoft SQL Server. Per saperne di più sulle CTE in T-SQL, vi consiglio il nostro corso interattivo Query ricorsive in MS SQL Server. Contiene oltre 100 esercizi pratici sulle CTE in T-SQL. Potrete mettere in pratica da soli tutto ciò che viene trattato in questo articolo! Espressioni di tabella comuni (CTE) in T-SQL Durante l'esecuzione di una query, è possibile fare riferimento alla CTE come a una qualsiasi tabella normale. Possono essere utilizzate nelle istruzioni SELECT, INSERT, UPDATE e DELETE. Le CTE non facevano parte del linguaggio SQL originale. Sono state introdotte come nuova funzionalità nella definizione dello standard SQL nel 1999 (SQL 3). Nel 2005 sono state rese disponibili in SQL Server. Le query SQL di esempio mostrate in questo articolo si baseranno sulla tabella olympic_games. La tabella contiene i risultati di tutti i giochi olimpici, compresi i 3 record (oro, argento e bronzo) per ogni sport. medal_idcityyearmedal_typewinner_namecountrysport 100Seoul1988GoldJohn DivKenyaMarathon Men 101Atlanta1996GoldKulus NamuKenya100 meters race 102Atlanta1996GoldPierre IzyFranceMarathon Men 103Barcelona1992GoldCarlos JerezSpain100 meters race 104Barcelona1992BronzePierre IzyFranceMarathon Men 105Atlanta1996SilverKulus NamuKenya100 meters race 106Barcelona1992GoldKulus NamuKenyaMarathon Men 107Barcelona1992GoldNala YeiEthiopiaMarathon Women 108Los Angeles1984GoldCarlos JerezSpain100 meters race 109Atlanta1996SilverJohn DivKenyaMarathon Men 110Barcelona1992SilverJean FlerFranceMarathon Men Esempio 1: una semplice CTE Per spiegare la sintassi CTE in T-SQL, scriviamo una semplice query. Supponiamo di voler creare un report con i Paesi e il numero di volte in cui ogni Paese ha vinto un metallo d'oro in una maratona. Possiamo creare una prima CTE chiamata gold_in_marathon per restituire le righe delle medaglie d'oro nelle maratone maschili e femminili. Nella CTE gold_in_marathon, abbiamo bisogno solo delle colonne city, year e country. Si noti che tutte le CTE iniziano con la parola chiave WITH, seguita da parentesi. Tra le parentesi si scrive la query che si vuole che la CTE restituisca. Di seguito è riportata la query completa. La CTE è evidenziata in grassetto: WITH gold_in_marathon AS ( SELECT city, year, country FROM olympic_games WHERE medal_type = 'Gold' AND sport IN ('Marathon Men', 'Marathon Women') ) SELECT country, count(*) AS gold_medals_in_marathon FROM gold_in_marathon GROUP BY country ORDER BY gold_medals_in_marathon DESC; Nella query T-SQL di cui sopra, è possibile identificare due query diverse. La prima è definita dalla clausola WITH racchiusa tra parentesi: è la query che definisce il contenuto della CTE. Si tratta di una normale query SQL: si possono inserire tutte le diverse caratteristiche SQL (WHERE, GROUP BY, HAVING, UNION, ecc.). Il database utilizza il risultato per creare una tabella virtuale chiamata gold_in_marathon. Si può vedere il nome della CTE (gold_in_marathon) dopo la clausola WITH. La seconda query è la query esterna. Fa riferimento alla CTE gold_in_marathon come a qualsiasi altra tabella. Al termine dell'esecuzione della query esterna, la CTE gold_in_marathon viene distrutta e non è più possibile farvi riferimento. Si noti che la CTE ha solo le colonne city, year e country della tabella olympic_games. Dopo aver creato la CTE, il database esegue la query esterna, che a sua volta legge la CTE gold_in_marathon, raggruppando le righe per country e utilizzando la funzione COUNT() per ottenere il numero di medaglie d'oro di ciascun Paese. Di seguito sono riportati i risultati della query: countrygold _medals_in_marathon Kenya2 Ethiopia1 France1 Esempio 2: Utilizzo di CTE con colonne rinominate (alias) in T-SQL Nella prossima query, rinomineremo esplicitamente una colonna nella CTE utilizzando un alias. Supponiamo di volere un report con la classifica nazionale dei risultati della maratona (donne e uomini). Ogni giocatore riceverà 3 punti per ogni medaglia d'oro, 2 punti per ogni medaglia d'argento e 1 punto per ogni medaglia di bronzo. Creeremo una CTE chiamata player_points per calcolare i punti di ciascun giocatore. Poiché utilizzeremo una colonna calcolata per i punti, dovremo assegnare un nome a questa colonna utilizzando un alias. Vediamo la query completa: WITH player_points AS ( SELECT country, winner_name, SUM( CASE medal_type WHEN 'Gold' THEN 3 WHEN 'Silver' THEN 2 WHEN 'Bronze' THEN 1 END ) AS player_total FROM olympic_games WHERE sport in ('Marathon Men', 'Marathon Women') GROUP BY country,winner_name ) SELECT country, SUM(player_total) AS country_points FROM player_points GROUP BY country ORDER BY country_points DESC; Nella CTE player_points, calcoliamo i punti di ogni giocatore utilizzando una colonna calcolata, che non ha un nome. Dobbiamo definire un nome per questa colonna, per potervi fare riferimento nella query esterna. Un modo per definire un nome è usare un alias (più avanti vedremo un altro modo) usando la clausola AS. Si può notare che la colonna è stata chiamata player_total nella definizione della CTE. Nella query esterna, raggruppiamo le righe per paese al fine di calcolare i punti totali di ciascun paese. Si noti che si utilizza l'espressione SUM(player_total) e che si utilizza un alias per rinominare la colonna in country_points. I risultati della query sono riportati di seguito: countryall_medals Kenya8 France6 Ethiopia3 Prima di chiudere questa sezione, vorrei suggerire l'articolo What Is a CTE in SQL Server?, dove si possono trovare molti esempi di query che utilizzano le CTE in T-SQL. L'articolo What Is a Common Table Expression (CTE) in SQL? tratta l'argomento delle CTE in SQL standard; entrambi gli articoli sono un buon complemento a questo. Se volete un corso sulle CTE e sulle query ricorsive, vi suggerisco di nuovo Query ricorsive in MS SQL Server. Esempio 3: Utilizzo di più CTE nella stessa query T-SQL In questa sezione mostreremo due esempi di query che utilizzano più di una CTE. Nel primo esempio, utilizzeremo due CTE indipendenti e la query principale accederà a entrambe. Supponiamo di voler creare un report con i nomi degli atleti olimpici che hanno vinto almeno una medaglia d'oro e una d'argento. La prima CTE si chiama gold. Dopo il nome della CTE, si possono vedere i nomi delle colonne (winner_name e gold_medals) definiti esplicitamente tra le parentesi. Questo è l'altro modo per rinominare una colonna nella CTE. Il nome della seconda CTE è silver e ha due colonne: winner_name e silver_medals. Si noti che non è stata inserita la clausola WITH prima della seconda CTE. La clausola WITH viene utilizzata una sola volta prima della definizione della prima CTE. Se si devono definire altre CTE, basta una virgola prima di iniziare la definizione delle CTE successive. WITH gold(winner_name,gold_medals) AS ( SELECT winner_name, count(*) FROM olympic_games WHERE medal_type = 'Gold' GROUP BY winner_name ), silver(winner_name,silver_medals) AS ( SELECT winner_name, count(*) FROM olympic_games WHERE medal_type = 'Silver' GROUP BY winner_name ) SELECT g.winner_name, g.gold_medals, s.silver_medals FROM gold g JOIN silver s ON g.winner_name = s.winner_name; Nella query T-SQL precedente, abbiamo creato due CTE: gold e silver con l'indirizzo winner_name e la quantità di medaglie (oro o argento) vinte da ciascun giocatore. Poi, nella query principale, abbiamo unito entrambe le CTE come se fossero tabelle normali, utilizzando la clausola JOIN. Poiché JOIN senza parole chiave funziona come una INNER JOIN, solo i record per la stessa winner_name in entrambe le tabelle saranno mostrati nel risultato della query. L'output è mostrato di seguito: winner_namegold_medalssilver_medals John Div11 Kulus Namu21 Esempio 4: Una query SQL Server con una CTE basata su un'altra CTE In seguito, creeremo due CTE in una query, ma la seconda CTE sarà basata sulla prima CTE. Supponiamo di volere una query per ottenere i 3 migliori Paesi per quantità di medaglie vinte in una partita olimpica. Non vogliamo ripetere i Paesi, quindi se la prima e la seconda posizione riguardano lo stesso Paese, vogliamo mostrarlo una sola volta. La query sarà la seguente: WITH medals AS ( SELECT city, year, country, COUNT(*) AS quantity_of_medals FROM olympic_games GROUP BY city, year, country ), country_best_game AS ( SELECT country, city, year, quantity_of_medals FROM medals m1 WHERE quantity_of_medals = ( SELECT max(quantity_of_medals) FROM medals m2 WHERE m1.country = m2.country ) ) SELECT TOP 3 country, city, year, quantity_of_medals FROM country_best_game ORDER BY quantity_of_medals DESC; In questa query, abbiamo creato una CTE chiamata medals con la quantità di medaglie ottenute da ciascun paese in ogni partita olimpica in cui il paese ha vinto almeno una medaglia. Successivamente, verrà calcolata una seconda CTE chiamata country_best_game (basata sulla prima CTE); essa contiene una riga per ogni paese con il numero massimo di medaglie ottenute in una singola partita. Infine, nella query principale, selezioniamo solo i tre Paesi con il maggior numero di medaglie vinte in una singola partita. I risultati della query sono riportati di seguito: countrycityyearquantity_of_medals KenyaAtlanta19963 FranceBarcelona19922 EthiopiaBarcelona19921 A questo punto, vorrei suggerirvi l'articolo Come imparare le espressioni di tabella comuni di SQL, dove potrete leggere i diversi approcci all'apprendimento delle CTE. Vedrete anche diversi esempi di query che utilizzano le CTE in SQL standard. Esempio 5: Utilizzo di una CTE in una INSERZIONE T-SQL In T-SQL, le CTE possono essere utilizzate anche nelle istruzioni UPDATE, INSERT e DELETE. Come regola generale, qualsiasi comando SQL che consenta un'istruzione SELECT incorporata (ad esempio un'istruzione CREATE VIEW ) può supportare una CTE. Vediamo un esempio di INSERT che utilizza un'espressione di tabella comune in T-SQL. Supponiamo di avere una tabella chiamata country_medals_by_game con le colonne country, city, game, number_of_medals e delta_with_previous_game. Il contenuto di ogni colonna è chiaro, tranne che per la colonna delta_with_previous_game. Tuttavia, questa colonna non verrà ancora utilizzata, quindi la spiegheremo in seguito. Il metodo INSERT per popolare la tabella è il seguente: WITH medals AS ( SELECT city, year, country, COUNT(*) AS medals_won FROM olympic_games GROUP BY city, year, country ) INSERT INTO country_medals_by_game(city,year,country,quantity_of_medals) SELECT city, year, country, medals_won FROM medals; Nel precedente INSERT abbiamo usato una CTE chiamata medals. Si noti che INSERT inizia con la definizione della CTE (si possono definire più CTE, se necessario). Dopo la definizione della CTE, inizia la normale istruzione INSERT. In questo esempio, INSERT utilizza un'istruzione SELECT, che a sua volta accede alla CTE medals definita in precedenza. Esempio 6: Utilizzo di una CTE in un UPDATE di SQL Server La colonna delta_with_previous_game memorizza la differenza di medaglie vinte da un Paese in due Olimpiadi consecutive. Se il Paese ha aumentato il numero di medaglie vinte, questa colonna avrà come valore positivo la differenza del numero di medaglie. Se il Paese ha vinto meno medaglie rispetto all'Olimpiade precedente, la colonna avrà un valore negativo. L'UPDATE per popolare la colonna è: WITH medals AS ( SELECT city, year, country, COUNT(*) AS medals_won FROM olympic_games GROUP BY city, year, country ) UPDATE country_medals_by_game SET delta_with_previous_game = ( SELECT COALESCE(quantity_of_medals - COALESCE(prevgame.medals_won,0),0) FROM medals prevgame WHERE country_medals_by_game.year = prevgame.year + 4 AND country_medals_by_game.country = prevgame.country ); In questo UPDATE, abbiamo iniziato con una clausola WITH per definire la stessa CTE medals che abbiamo utilizzato. Quando la sezione di definizione della CTE termina, inizia l'istruzione UPDATE. Nella clausola SET si utilizza una subquery per calcolare la differenza di medaglie vinte in due Olimpiadi consecutive. Si noti che la sottoquery accede alla CTE medals e la condizione ... country_medals_by_game.year = prevgame.year + 4 ... è di abbinare una riga di country_medals_by_game con la riga delle medaglie della partita olimpica precedente (avvenuta quattro anni prima). Una cosa interessante da notare è questa: Per i paesi che non hanno partecipato a due giochi contigui, impostiamo la colonna delta_with_previous_game su NULL. Questo indica che non possiamo calcolare la differenza; usare uno zero per questa colonna non è corretto perché significherebbe che il paese ha partecipato al gioco precedente, cosa che non ha fatto. Esempio 7: Utilizzo di CTE per Query ricorsive in T-SQL In SQL Server è comune avere tabelle che rappresentano gerarchie di dati (come dipendenti-gestori, parti-sottoparti o genitori-figli). Per attraversare queste gerarchie in qualsiasi direzione, dall'alto verso il basso o dal basso verso l'alto, SQL Server utilizza un costrutto chiamato CTE ricorsivo. Per avere una gerarchia di dati nel nostro database Olympic, aggiungeremo una coppia di colonne alla tabella olympic_games. Supponiamo di voler identificare le medaglie che rappresentano un record mondiale. Possiamo aggiungere una colonna di testo chiamata record e impostarla con true quando una medaglia è associata a un nuovo record mondiale. Inoltre, sappiamo che ogni record infrange un record precedente, quindi aggiungeremo un'altra colonna chiamata previous_record_medal in cui inseriremo medal_id del record precedente. Ora abbiamo una gerarchia di dati da leggere con una query ricorsiva; mostriamo una vista parziale della tabella olympic_games con le nuove colonne: medal_idcityyearmedal_typesportrecordprevious_record_medal 100Seoul1988GoldMarathon MenfalseNULL 101Atlanta1996Gold100 meters racetrue103 102Atlanta1996GoldMarathon Mentrue106 103Barcelona1992Gold100 meters racefalse108 104Barcelona1992BronzeMarathon MenfalseNULL 105Atlanta1996Silver100 meters racefalseNULL 106Barcelona1992GoldMarathon Menfalse100 107Barcelona1992GoldMarathon WomenfalseNULL 108Los Angeles1984Gold100 meters racefalseNULL 109Atlanta1996SilverMarathon MenfalseNULL 110Barcelona1992SilverMarathon MenfalseNULL Supponiamo di voler ottenere un report con l'elenco dei record mondiali nella maratona maschile. Possiamo iniziare mostrando il record mondiale attuale, poi quello immediatamente precedente e così via. Avremo bisogno di una CTE ricorsiva per attraversare la gerarchia dei record della maratona maschile. Il suo aspetto sarà il seguente: WITH record_history (medal_id, year, winner, country, prev_record_medal_id) AS ( SELECT medal_id, year, winner_name, country, previous_record_medal FROM olympic_games WHERE sport = 'Marathon Men' AND record = 'true' UNION ALL SELECT og.medal_id, og.year, og.winner_name, og.country, og.previous_record_medal FROM olympic_games og JOIN record_history mrh ON og.medal_id = mrh.prev_record_medal_id ) SELECT * FROM record_history; La CTE record_history è ottenuta come risultato di una UNION ALL. La prima query nella UNION è per ottenere il record mondiale attuale; si noti la condizione record = true. Dopo UNION ALL, abbiamo un'altra query che ottiene tutti i record precedenti nella maratona maschile. La chiave per collegare una riga di record di medaglie con la riga di record di medaglie precedente è la condizione: og.medal_id = mrh.prev_record_medal_id I risultati della query sono riportati di seguito: medal_idyearwinner_namecountryprevious_record_medal 1021996Pierre IzyFrance106 1061992Kulus NamuKenya100 1001998John DivKenyaNULL Per evitare un ciclo infinito in una CTE ricorsiva, esiste un limite al numero di invocazioni consentite. In SQL Server, questo limite è definito per impostazione predefinita come 100. Tuttavia, è possibile modificare questo limite utilizzando il parametro MAXRECURSION alla fine della query ricorsiva. Se volete approfondire il tema delle query ricorsive, vi suggerisco gli articoli Come scrivere una CTE ricorsiva in SQL Server e Fallo in SQL: L'attraversamento ricorsivo dell'albero in SQL. Troverete numerosi esempi e diversi approcci per spiegare le query ricorsive. Le CTE sono una potente caratteristica del linguaggio T-SQL In questo articolo abbiamo illustrato come utilizzare le CTE di T-SQL per semplificare le query complesse per i database di SQL Server. Prima di concludere, vorrei suggerire alcuni articoli relativi a SQL Server. Il primo è Top 5 SQL CTE Interview Questions, dove si possono trovare consigli su come gestire un colloquio per un lavoro in SQL. Un altro articolo interessante è Come installare Microsoft SQL Server 2019 e SQL Server Management Studio, dove potete trovare assistenza per l'installazione di SQL Server. Infine, vorrei incoraggiarvi a dare un'occhiata al corso Query ricorsive in MS SQL Server. Potrete imparare come elaborare alberi e grafici in T-SQL e come organizzare efficacemente le vostre query. Sviluppate le vostre competenze e aumentate il vostro patrimonio! Tags: sql cte T-SQL