20th Sep 2023 Lectura de 13 minutos ¿Qué es una CTE en T-SQL? Una Guía en Profundidad para Principiantes con 7 Ejemplos Ignacio L. Bisso SQL CTE T-SQL Índice Expresiones Comunes de Tabla (CTEs) en T-SQL Ejemplo 1: Una CTE sencilla Ejemplo 2: Uso de CTEs con Columnas Renombradas (Aliased) en T-SQL Ejemplo 3: Uso de Múltiples CTEs en la Misma Consulta T-SQL Ejemplo 4: Una Consulta SQL Server con un CTE Basado en Otro CTE Ejemplo 5: Uso de una CTE en un INSERT T-SQL Ejemplo 6: Uso de una CTE en un UPDATE de SQL Server Ejemplo 7: Uso de CTEs para Consultas recursivas y expresiones de tabla comunes en T-SQL Las CTEs son una Poderosa Característica del Lenguaje T-SQL Una expresión común de tabla (CTE) es una poderosa característica T-SQL que simplifica la creación de consultas en SQL Server. Las CTE funcionan como tablas virtuales (con registros y columnas) que se crean sobre la marcha durante la ejecución de una consulta. Son consumidas por la consulta y destruidas después de que ésta se ejecute. En algunos casos - como cuando la consulta espera datos en un formato específico y las tablas de origen tienen los datos en otro formato - una CTE puede actuar como puente para transformar los datos de las tablas de origen al formato esperado por la consulta. En este artículo veremos cómo crear una CTE en T-SQL, el dialecto SQL utilizado por Microsoft SQL Server. Para obtener más información sobre las CTE en T-SQL, le recomiendo nuestro curso interactivo Consultas recursivas y expresiones de tabla comunes en MS SQL Server. Contiene más de 100 ejercicios prácticos sobre CTEs en T-SQL. ¡Practicarás por tu cuenta todo lo tratado en este artículo! Expresiones Comunes de Tabla (CTEs) en T-SQL Durante la ejecución de una consulta, puedes referirte a la CTE como lo harías con cualquier tabla regular. Se puede utilizar en las sentencias SELECT, INSERT, UPDATE, y DELETE. Las CTE no formaban parte del lenguaje SQL original. Se introdujeron como una nueva característica en la definición estándar de SQL en 1999 (SQL 3). En 2005, estuvieron disponibles en SQL Server. Las consultas SQL de ejemplo que se muestran en este artículo se basan en la tabla olympic_games. La tabla contiene los resultados de todos los Juegos Olímpicos, incluyendo 3 récords (oro, plata y bronce) para cada deporte. 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 Ejemplo 1: Una CTE sencilla Para explicar la sintaxis CTE en T-SQL, escribamos una consulta sencilla. Supongamos que queremos un informe con los países y el número de veces que cada país ha ganado un metal de oro en un maratón. Podemos crear primero una CTE llamada gold_in_marathon para que nos devuelva esas filas de medallas de oro en maratones masculinos y femeninos. En el CTE gold_in_marathon, sólo necesitamos las columnas city, year y country. Tenga en cuenta que todas las ETC comienzan con la palabra clave WITH, seguida de un paréntesis. En los paréntesis, escriba la consulta que desea que devuelva el CTE. A continuación se muestra la consulta completa. Con la CTE en negrita: 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; En la consulta T-SQL anterior, puede identificar dos consultas diferentes. La primera está definida por la cláusula WITH entre paréntesis: es la consulta que define el contenido del CTE. Se trata de una consulta SQL normal: puede poner todas las características SQL diferentes (WHERE, GROUP BY, HAVING, UNION, etc.). La base de datos utiliza el resultado para crear una tabla virtual llamada gold_in_marathon. Puede ver el nombre del CTE (gold_in_marathon) después de la cláusula WITH. La segunda consulta es la consulta externa. Hace referencia al CTE gold_in_marathon como cualquier otra tabla. Cuando finaliza la ejecución de la consulta externa, el CTE gold_in_marathon se destruye y ya no se puede hacer referencia a él. Observe que el CTE sólo tiene las columnas city, year, y country de la tabla olympic_games. Después de crear el CTE, la base de datos ejecuta la consulta externa, que a su vez lee el CTE gold_in_marathon, agrupando las filas por country y utilizando la función COUNT() para obtener cuántas medallas de oro tiene cada país. A continuación se muestran los resultados de la consulta: countrygold _medals_in_marathon Kenya2 Ethiopia1 France1 Ejemplo 2: Uso de CTEs con Columnas Renombradas (Aliased) en T-SQL En la siguiente consulta, cambiaremos explícitamente el nombre de una columna en la CTE utilizando un alias. Supongamos que queremos un informe con la clasificación por países de los resultados de maratón (femenino y masculino). Cada jugador obtendrá 3 puntos por cada medalla de oro, 2 puntos por cada medalla de plata y 1 punto por cada medalla de bronce. Crearemos un CTE llamado player_points para calcular los puntos de cada jugador. Como utilizaremos una columna calculada para los puntos, tendremos que asignar un nombre a esta columna utilizando un alias. Veamos la consulta 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; En el CTE player_points, calculamos los puntos de cada jugador utilizando una columna calculada, que no tiene nombre. Necesitamos definir un nombre para esta columna para poder referirnos a ella en la consulta externa. Una forma de definir un nombre es mediante un alias (más adelante veremos otra forma) utilizando la cláusula AS. Puede ver que la columna fue nombrada player_total en la definición del CTE. En la consulta externa, agrupamos las filas por país para calcular el total de puntos de cada país. Nótese que utilizamos la expresión SUM(player_total) y de nuevo utilizamos un alias para renombrar la columna a country_points. Los resultados de la consulta se muestran a continuación: countryall_medals Kenya8 France6 Ethiopia3 Antes de cerrar esta sección me gustaría sugerirte el artículo ¿Qué es una CTE en SQL Server?, donde puedes encontrar muchos ejemplos de consultas usando CTEs en T-SQL. El artículo ¿Qué es una Expresión de Tabla Común (CTE) en SQL? cubre el tema de las CTEs en SQL estándar; ambos artículos son un buen complemento a éste. Si desea un curso sobre CTEs y consultas recursivas, le sugiero nuevamente Consultas recursivas y expresiones de tabla comunes en MS SQL Server. Ejemplo 3: Uso de Múltiples CTEs en la Misma Consulta T-SQL En esta sección, mostraremos dos ejemplos de consultas que utilizan más de una CTE. En el primer ejemplo, utilizaremos dos CTEs independientes y la consulta principal accederá a ambos. Supongamos que queremos un informe con los nombres de los atletas olímpicos que ganaron al menos una medalla de oro y una de plata. El primer CTE se llama gold. Después del nombre del CTE, puede ver los nombres de las columnas (winner_name y gold_medals) definidos explícitamente dentro de los paréntesis. Esta es la otra forma de renombrar una columna en el CTE. El nombre del segundo CTE es silver y tiene dos columnas: winner_name y silver_medals. Observe que no hemos puesto la cláusula WITH antes del segundo CTE. La cláusula WITH sólo se utiliza una vez antes de la definición del primer CTE. Si tenemos más CTEs que definir, sólo necesitamos una coma antes de empezar la definición del siguiente(s) CTE(s). 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; En la consulta T-SQL anterior, creamos dos CTEs: gold y silver con el winner_name y la cantidad de medallas (oro o plata) ganadas por cada jugador. A continuación, en la consulta principal unimos ambos CTE como si fueran tablas normales utilizando una cláusula JOIN. Como JOIN sin palabras clave funciona como INNER JOIN, sólo se mostrarán en el resultado de la consulta los registros del mismo winner_name en ambas tablas. A continuación se muestra el resultado: winner_namegold_medalssilver_medals John Div11 Kulus Namu21 Ejemplo 4: Una Consulta SQL Server con un CTE Basado en Otro CTE A continuación, crearemos dos CTEs en una consulta; sin embargo, el segundo CTE estará basado en el primer CTE. Supongamos que queremos una consulta para obtener el TOP 3 de países por la cantidad de medallas ganadas en un juego Olímpico. No queremos repetir países, así que si la primera y la segunda posición son para el mismo país, queremos mostrar este país sólo una vez. La consulta será la siguiente: 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; En esta consulta, creamos un CTE llamado medals con la cantidad de medallas obtenidas por cada país en cada juego olímpico donde el país ganó al menos una medalla. Después, calcularemos un segundo CTE llamado country_best_game (basado en el primer CTE); tiene una fila para cada país con el número máximo de medallas obtenidas en un solo juego. Por último, en la consulta principal, seleccionamos sólo los tres países con el mayor número de medallas obtenidas en un solo partido. A continuación se muestran los resultados de la consulta: countrycityyearquantity_of_medals KenyaAtlanta19963 FranceBarcelona19922 EthiopiaBarcelona19921 En este punto, me gustaría sugerirte el artículo Cómo aprender expresiones comunes de tabla SQL, donde puedes leer sobre diferentes enfoques para aprender CTEs. También verás varios ejemplos de consultas usando CTEs en SQL estándar. Ejemplo 5: Uso de una CTE en un INSERT T-SQL En T-SQL, las CTEs también pueden utilizarse en las sentencias UPDATE, INSERT y DELETE. Como regla general, cualquier comando SQL que permita un SELECT incrustado (por ejemplo una sentencia CREATE VIEW ) puede soportar un CTE. Veamos un ejemplo de INSERT utilizando una expresión de tabla común en T-SQL. Supongamos que tenemos una tabla llamada country_medals_by_game con las columnas country, city, game, number_of_medals, y delta_with_previous_game. El contenido de cada columna es claro, excepto el de la columna delta_with_previous_game. Sin embargo, todavía no vamos a utilizar esta columna, por lo que la explicaremos más adelante. El INSERT para rellenar la tabla es el siguiente: 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; En el INSERT anterior utilizamos un CTE llamado medals. Observe que el INSERT comienza con la definición del CTE (puede definir múltiples CTEs si lo necesita). Una vez definido el CTE, comienza la sentencia normal INSERT. En este ejemplo, INSERT utiliza una sentencia SELECT, que a su vez accede al CTE medals definido previamente. Ejemplo 6: Uso de una CTE en un UPDATE de SQL Server La columna delta_with_previous_game almacena la diferencia de medallas ganadas por un país en dos Juegos Olímpicos consecutivos. Si el país aumentó el número de medallas ganadas, esta columna tendrá la diferencia en el número de medallas como valor positivo. Si el país ganó menos medallas que en los Juegos Olímpicos anteriores, la columna tendrá un valor negativo. El UPDATE para rellenar la columna es: 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 ); En este UPDATE, comenzamos con una cláusula WITH para definir la misma CTE medals que hemos estado utilizando. Cuando termina la sección de definición de la CTE, comienza la sentencia UPDATE. En la cláusula SET, utilizamos una subconsulta para calcular la diferencia de medallas ganadas en dos juegos olímpicos consecutivos. Observe que la subconsulta accede a la CTE medals y la condición ... country_medals_by_game.year = prevgame.year + 4 ... es hacer coincidir una fila de country_medals_by_game con la fila de medallas del juego olímpico anterior (que tuvo lugar cuatro años antes). Un dato interesante es el siguiente: Para los países que no participaron en dos juegos contiguos, establecemos la columna delta_with_previous_game en NULL. Esto indica que no podemos calcular la diferencia; utilizar un cero para esta columna es incorrecto porque significaría que el país participó en el juego anterior, cosa que no hizo. Ejemplo 7: Uso de CTEs para Consultas recursivas y expresiones de tabla comunes en T-SQL En SQL Server, es habitual tener tablas que representan jerarquías de datos (como empleado-gerente, parte-subparte o padre-hijo). Para recorrer estas jerarquías en cualquier dirección (de arriba abajo o de abajo arriba), SQL Server utiliza una construcción denominada CTE recursiva. Para tener una jerarquía de datos en nuestra base de datos olímpica, añadiremos un par de columnas a la tabla olympic_games. Supongamos que queremos identificar las medallas que representan un récord mundial. Podemos añadir una columna de texto llamada record y establecerla con true cuando una medalla esté asociada a un nuevo récord mundial. Además, sabemos que todo récord bate un récord anterior, así que añadiremos otra columna llamada previous_record_medal donde pondremos el medal_id del récord anterior. Ahora tenemos una jerarquía de datos para leer con una consulta recursiva; mostremos una vista parcial de la tabla olympic_games con sus nuevas columnas: 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 Supongamos que queremos obtener un informe con la lista de récords del mundo de maratón masculino. Podemos empezar mostrando el récord mundial actual, y después mostrar el récord inmediatamente anterior, y así sucesivamente. Necesitaremos una CTE recursiva para recorrer la jerarquía de récords masculinos de maratón. Tendrá el siguiente aspecto: 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; El CTE record_history se obtiene como resultado de un UNION ALL. La primera consulta en el UNION es para obtener el récord mundial actual; nótese la condición record = true. Después del UNION ALL, tenemos otra consulta que obtiene todos los récords anteriores en el maratón masculino. La clave para conectar una fila de récords con la fila de récords anteriores es la condición: og.medal_id = mrh.prev_record_medal_id A continuación se muestran los resultados de la consulta: medal_idyearwinner_namecountryprevious_record_medal 1021996Pierre IzyFrance106 1061992Kulus NamuKenya100 1001998John DivKenyaNULL Para evitar un bucle infinito en una CTE recursiva, existe un límite en el número de invocaciones permitidas. En SQL Server, este límite está definido por defecto en 100. Sin embargo, puedes cambiar este límite usando el parámetro MAXRECURSION al final de la consulta recursiva. Si quieres profundizar en las consultas recursivas, te sugiero los artículos Cómo escribir una CTE recursiva en SQL Server y Hazlo en SQL: Recursive SQL Tree Traversal. Encontrarás varios ejemplos y diferentes enfoques para explicar las consultas recursivas. Las CTEs son una Poderosa Característica del Lenguaje T-SQL En este artículo, cubrimos cómo usar CTEs T-SQL para simplificar consultas complejas para bases de datos SQL Server. Antes de terminar, me gustaría sugerir algunos artículos relacionados con SQL Server. El primero es Top 5 SQL CTE Interview Questions, donde puedes encontrar consejos sobre cómo gestionar una entrevista para un trabajo de SQL. Otro artículo interesante es Cómo instalar Microsoft SQL Server 2019 y SQL Server Management Studio, donde podrás encontrar ayuda para instalar SQL Server. Por último me gustaría animarte a que eches un vistazo al curso Consultas recursivas y expresiones de tabla comunes en MS SQL Server. Podrás aprender a procesar árboles y gráficos en T-SQL y a organizar eficazmente tus consultas. ¡Desarrolla tus habilidades y aumenta tus activos! Tags: SQL CTE T-SQL