Volver a la lista de artículos Artículos
Lectura de 10 minutos

¿Qué es una CTE recursiva en SQL?

El artículo que le mostrará ejemplos prácticos del uso de CTEs recursivas en SQL.

Si has oído hablar de las CTEs recursivas de SQL pero nunca las has utilizado, este artículo es para ti. También es para ti si nunca te cansas de los ejemplos de CTE recursivas.

Antes de profundizar en la recursión, te recordaré qué son las CTEs y cuál es su sintaxis. Luego haré lo mismo con las CTEs recursivas. Después, te mostraré cómo funcionan las CTEs recursivas en tres ejemplos.

¿Qué son las CTEs?

La CTE (expresión de tabla común), también conocida como cláusula WITH, es una función de SQL que devuelve un conjunto de datos temporal que puede ser utilizado por otra consulta. Como es un resultado temporal, no se almacena en ningún sitio, pero se puede referenciar como se haría con cualquier otra tabla.

Hay dos tipos de CTEs, los no recursivos y los recursivos.

Aquí hay un buen artículo que te mostrará qué son las CTEs y cómo funcionan.

Sintaxis de las CTEs no recursivas

La sintaxis general de una CTE no recursiva tiene el siguiente aspecto:

WITH cte_name AS (cte_query_definition)

SELECT *
FROM   cte_name;

La primera parte de la sintaxis es el CTE. Comienza con la palabra clave WITH. Luego se le da un nombre al CTE. Después de la palabra clave AS, puede definir el CTE en los paréntesis.

La segunda parte de la sintaxis es una simple sentencia SELECT. Se escribe inmediatamente después de la CTE recursiva, sin ninguna coma, punto y coma o marcas similares. Como dije antes, el CTE se utiliza en otra consulta como cualquier otra tabla. Esto es exactamente lo que hace la sentencia SELECT.

Aquí está el artículo que puede ayudarte adicionalmente con la sintaxis CTE y sus reglas. Y si necesitas más ejemplos de CTE, este artículo es para ti.

Sintaxis CTE Recursiva

Una CTE recursiva hace referencia a sí misma. Devuelve el subconjunto de resultados, luego se referencia a sí misma repetidamente (recursivamente), y se detiene cuando devuelve todos los resultados.

La sintaxis de un CTE recursivo no es muy diferente de la de un CTE no recursivo:

WITH RECURSIVE cte_name AS (
    cte_query_definition (the anchor member)

    UNION ALL

    cte_query_definition (the recursive member)
)


SELECT *
FROM   cte_name;

De nuevo, al principio de tu CTE está la cláusula WITH. Sin embargo, si quieres que tu CTE sea recursiva, entonces después de WITH escribes la palabra clave RECURSIVE. A continuación, todo sigue como de costumbre: a AS le siguen los paréntesis con la definición de la consulta CTE. Esta primera definición de consulta se llama miembro ancla.

Para conectar el miembro ancla con el miembro recursivo, es necesario utilizar el comando UNION o UNION ALL. El miembro recursivo es, obviamente, la parte recursiva del CTE que hará referencia al propio CTE. Verás cómo funciona en un ejemplo muy pronto.

Los CTEs recursivos se utilizan principalmente cuando se desea consultar datos jerárquicos o gráficos. Puede tratarse de la estructura organizativa de una empresa, un árbol genealógico, el menú de un restaurante o varias rutas entre ciudades. Consulte estos artículos para entender cómo funcionan los CTEs con estructuras jer árquicas y cómo consultar datos de gráficos.

Ahora que entendemos cómo funcionan las CTEs recursivas, veamos algunos ejemplos.

Ejemplo 1 - Encontrar jefes y nivel jerárquico para todos los empleados

Para este problema, utilizaré los datos de la tabla employeesque tiene las siguientes columnas:

  • id: El ID del empleado.
  • first_name: El nombre del empleado.
  • last_name: Los apellidos del empleado.
  • boss_id: La identificación del jefe del empleado.

Este es el aspecto de los datos:

idfirst_namelast_nameboss_id
1DomenicLeaver5
2ClevelandHewins1
3KakalinaAtherton8
4RoxannaFairlieNULL
5HermieComsty4
6PoohGoss8
7FaulknerChalliss5
8BobbeBlakeway4
9LaureneBurchill1
10AugustaGosdin8

No es demasiado complicado. Por ejemplo, el jefe de Domenic Leaver es el empleado con el ID de 5; es decir, Hermie Comsty. El mismo principio funciona para todos los demás empleados, excepto Roxanna Fairlie. Ella no tiene jefe; hay un valor NULL en la columna boss_id. Podemos concluir que Roxanna es la presidenta o propietaria de la empresa.

Ahora escribamos el CTE recursivo para listar todos los empleados y sus jefes directos.

WITH RECURSIVE company_hierarchy AS (
  SELECT	id,
    		first_name,
    		last_name,
    		boss_id,
		0 AS hierarchy_level
  FROM employees
  WHERE boss_id IS NULL

  UNION ALL 
  
  SELECT	e.id,
    		e.first_name,
    		e.last_name,
    		e.boss_id, 
		hierarchy_level + 1
  FROM employees e, company_hierarchy ch
  WHERE e.boss_id = ch.id
)

SELECT   ch.first_name AS employee_first_name,
	   ch.last_name AS employee_last_name,
	   e.first_name AS boss_first_name,
	   e.last_name AS boss_last_name,
	   hierarchy_level
FROM company_hierarchy ch
LEFT JOIN employees e
ON ch.boss_id = e.id
ORDER BY ch.hierarchy_level, ch.boss_id;

¿Qué hace esta consulta? Es una consulta recursiva, por lo que comienza con WITH RECURSIVE. El nombre del CTE es company_hierarchy. Después de AS, la definición del CTE está entre paréntesis.

La primera sentencia SELECT selecciona todas las employee tabla donde la columna boss_id es NULL. En resumen, seleccionará a Roxanna Fairlie, porque sólo ella tiene un valor NULL en esa columna. Aún más corto: estoy comenzando la recursión desde la parte superior de la estructura organizativa. También hay una columna hierarchy_level con el valor 0. Eso significa que el nivel del propietario/presidente es 0 - están en la cima de la jerarquía.

He utilizado el UNION ALL para conectar esta sentencia SELECT con la segunda, es decir, con el miembro recursivo. En el miembro recursivo, estoy seleccionando todas las columnas de la tabla employees y el CTE company_hierarchy donde la columna boss_id es igual a la columna id. Fíjate en la parte hierarchy_level + 1. Esto significa que con cada recursión, el CTE sumará 1 al nivel jerárquico anterior, y lo hará hasta llegar al final de la jerarquía. Observe también que estoy tratando esta CTE como cualquier otra tabla. Para terminar de definir el CTE, simplemente cierre los paréntesis.

Finalmente, hay una tercera sentencia SELECT, fuera del CTE. Selecciona las columnas que mostrarán los empleados, los nombres de sus jefes y el nivel jerárquico. Los datos se toman del CTE y de la tabla employees. He unido esos dos con un LEFT JOIN, ya que quiero todos los datos del CTE - incluyendo a Roxanna Fairlie, que tiene el valor NULL en la columna boss_id. El resultado se mostrará en orden ascendente: primero por el nivel de jerarquía, luego por el ID del jefe. Así es como se ve:

employee_first_nameemployee_last_nameboss_first_nameboss_last_namehierarchy_level
RoxannaFairlieNULLNULL0
HermieComstyRoxannaFairlie1
BobbeBlakewayRoxannaFairlie1
DomenicLeaverHermieComsty2
FaulknerChallissHermieComsty2
AugustaGosdinBobbeBlakeway2
PoohGossBobbeBlakeway2
KakalinaAthertonBobbeBlakeway2
LaureneBurchillDomenicLeaver3
ClevelandHewinsDomenicLeaver3

Roxanna Fairlie es la jefa final; eso ya lo sabías. Hay dos empleados en el nivel 1. Esto significa que Bobbe Blakeway y Hermie Comsty son los subordinados directos de Roxanna Fairlie. En el nivel 2, hay empleados cuyos jefes directos son Bobbe Blakeway y Hermie Comsty. También hay un tercer nivel en la jerarquía. Se trata de empleados cuyo jefe directo es Domenic Leaver.

Ejemplo 2 - Hallar el importe de la inversión por inversor

En este ejemplo, utilizaré la tabla investment:

  • id: El ID de la inversión.
  • investment_amount: El importe de la inversión.

Los datos de la tabla tienen este aspecto:

idinvestment_amount
19,705,321.00
25,612,948.60
35,322,146.00

Estos son los importes de las tres posibles opciones de inversión. Serán considerados por los tres inversores, que dividirán el importe total de la inversión en partes iguales. Su tarea consiste en calcular el importe por inversor en función de su número, es decir, si invierten uno, dos, tres o ningún inversor en cada inversión.

La consulta que resuelve este problema es:

WITH RECURSIVE per_investor_amount AS (
	SELECT	0 AS investors_number,
			0.00 AS investment_amount,
			0.00 AS individual_amount
	UNION 

	SELECT	investors_number + 1,
			i.investment_amount,
			i.investment_amount / (investors_number + 1)
	FROM investment i, per_investor_amount pia
	WHERE investors_number << 3
)

SELECT *
FROM per_investor_amount
ORDER BY  investment_amount, investors_number;

Una vez más, el CTE comienza con WITH RECURSIVE, seguido de su nombre y la definición de la consulta. Esta vez, utilizaré el miembro ancla de la consulta recursiva para crear algunos datos. Las columnas son investors_number, investment_amount, y individual_amount. Este es el punto desde el que quiero que empiece la recursión (igual que en el ejemplo anterior, con hierarchy_level = 0).

Luego viene el UNION y el miembro recursivo. Esta parte de la consulta incrementará la columna investors_number en uno con cada recursión. Lo hará para cada investment_amount. La tercera columna calculará el importe de esa inversión por inversor, en función del número de inversores que participen. La recursión se realizará para un máximo de tres inversores (es decir, hasta alcanzar la condición WHERE investors_number < 3).

Después viene la simple sentencia SELECT que devolverá todas las columnas del CTE. Y aquí está el resultado:

investors_numberinvestment_amountindividual_amount
00.000.00
15,322,146.005,322,146.00
25,322,146.002,661,073.00
35,322,146.001,774,048.67
15,612,948.605,612,948.60
25,612,948.602,806,474.30
35,612,948.601,870,982.87
19,705,321.009,705,321.00
29,705,321.004,852,660.50
39,705,321.003,235,107.00

No es difícil de analizar. Si no hay inversores, el importe de la inversión es cero, y también el importe individual. Si la inversión es de 5.322.146,00 y sólo hay un inversor, el importe por inversor será de 5.322.146,00. Si hay dos inversores con el mismo importe, entonces cada uno de ellos tendrá que pagar 2.661.073,00. Si los tres inversores deciden invertir, cada uno pagará 1.774.048,67. Las otras dos cantidades de inversión siguen el mismo patrón, como se puede ver en la tabla.

Ejemplo 3 - Encontrar rutas entre ciudades

En el tercer ejemplo, utilizaré la tabla cities_routeque contiene datos sobre las ciudades holandesas:

  • city_from: La ciudad de salida.
  • city_to: La ciudad de destino.
  • distance: La distancia entre dos ciudades, en kilómetros.
city_fromcity_todistance
GroningenHeerenveen61.4
GroningenHarlingen91.6
HarlingenWieringerwerf52.3
WieringerwerfHoorn26.5
HoornAmsterdam46.1
AmsterdamHaarlem30
HeerenveenLelystad74
LelystadAmsterdam57.2

Utilice esta tabla para encontrar todas las rutas posibles de Groningen a Haarlem, mostrando las ciudades en la ruta y la distancia total.

Esta es la consulta para resolver este problema:

WITH RECURSIVE possible_route AS (
	SELECT	cr.city_to,
       		cr.city_from || '->' ||cr.city_to AS route,
       		cr.distance
      FROM cities_route cr
      WHERE cr.city_from = 'Groningen'

UNION ALL

SELECT 	cr.city_to,
       		pr.route || '->' || cr.city_to AS route,
        		CAST((pr.distance + cr.distance) AS DECIMAL(10, 2))
      FROM possible_route pr
INNER JOIN cities_route cr
      		ON cr.city_from = pr.city_to
)

SELECT 	pr.route,
		pr.distance
FROM possible_route pr
WHERE pr.city_to = 'Haarlem'
ORDER BY pr.distance;

Veamos qué hace esta consulta. La primera sentencia SELECT de la definición del CTE seleccionará las columnas de la tabla cities_route donde la ciudad de salida es Groningen. Observa que también hay una nueva columna llamada ruta, que utilizaré para concatenar las ciudades de la ruta.

El UNION ALL conecta esto con el miembro recursivo. Esta sentencia SELECT seleccionará la ciudad de llegada, concatenará las ciudades de la ruta y, finalmente, sumará las distancias entre estas ciudades al total de la ruta entre Groningen y Haarlem. Para conseguir todo eso, he unido el CTE con la tabla cities_route.

Luego viene la sentencia SELECT que extrae los datos del CTE. Seleccionará la ruta y la distancia cuando la ciudad de llegada sea Haarlem, con los datos ordenados por distancia en orden ascendente.

El resultado de la consulta tiene el siguiente aspecto:

routedistance
Groningen->Heerenveen->Lelystad->Amsterdam->Haarlem222.6
Groningen->Harlingen->Wieringerwerf->Hoorn->Amsterdam->Haarlem246.5

No es difícil entender esta tabla. Hay dos rutas de Groningen a Haarlem. Incluyen diferentes ciudades entre ellas y tienen una longitud de 222,6 km y 246,5 km, respectivamente.

Si quieres seguir aprendiendo, comprueba cómo puedes utilizar una CTE recursiva en lugar de una consulta SQL larga. Y después de abordar ese tema, diviértete un poco dibujando algo utilizando una CTE recursiva.

Seguir practicando las CTEs Recursivas

Estos tres ejemplos han demostrado las posibilidades de las CTEs recursivas en SQL. Ahora es el momento de ampliar lo que has aprendido aquí.

Probablemente, la mejor opción es consultar nuestro curso Consultas recursivas y expresiones de tabla comunes . Te ofrece un montón de ejemplos, explicaciones y oportunidades de práctica. El curso forma parte de la pista del curso SQL avanzado , donde puedes aprender sobre otros temas avanzados de SQL como las funciones de ventana, las extensiones GROUP BY y las consultas recursivas.

¡Diviértete!