Una técnica muy poderosa aunque muy poco usada es la de la recursividad. ¿Qué significa recursividad? Que un código se ejecute y al finalizar se llame a sí mismo para volver a ejecutarse. Sin embargo, al volver a ejecutarse lo hace con algún parámetro cambiado y continúa llamándose a sí mismo hasta que se cumpla una condición de fin. Sin esa condición de fin se ejecutaría infinitas veces (o más propiamente, hasta que usara toda la memoria disponible de la computadora).

En Firebird podemos tener stored procedures recursivos y también SELECTs recursivos.

Los stored procedures recursivos ya los vimos en el artículo:

Stored procedures recursivos

Así que ahora veremos algo también muy interesante, los SELECTs recursivos.

Para crear un SELECT recursivo debemos usar CTE (Common Table Expression) porque esa construcción permite recursividad.

Ejemplo:

Tenemos una tabla llamada GENEALOGIA donde guardamos los nombres de las personas y nos gustaría conocer quienes son los ascendientes de cada persona (o sea, sus padres, abuelos, bisabuelos, tatarabuelos, etc.)

Algo así podríamos conseguir con tablas autoreferenciadas, como vimos en el artículo:

Entendiendo las tablas autoreferenciadas

y también con recursividad, que es el tema que ahora nos ocupa.

RECURSIVO02

Captura 1. Si haces clic en la imagen la verás más grande

RECURSIVO03

Captura 2. Si haces clic en la imagen la verás más grande

En la columna GEN_IDEPAD se guarda el Identificador de la fila padre. Una fila puede no tener fila padre (como es el caso de «ALICIA» o puede tener una sola fila padre (los demás casos).

Gráficamente, lo que tenemos es esto:

RECURSIVO01

Captura 3. Si haces clic en la imagen la verás más grande

Aquí podemos ver que BRIGIDA, BEATRIZ y BENITA son hijas de ALICIA; y que CORINA, CAROL, CARMEN y CLAUDIA son hijas de BRIGIDA y que DALILA y DIANA son hijas de CAROL.

Entonces, para ver el nombre de una persona y de todos sus ascendientes podríamos escribir algo como:

Listado 1.

WITH RECURSIVE PARIENTES AS (
   
   SELECT 
      * 
   FROM 
      GENEALOGIA 
   WHERE 
      GEN_IDENTI = 10
   
   UNION ALL
   
   SELECT 
      GENEALOGIA.* 
   FROM 
      GENEALOGIA 
   JOIN 
      PARIENTES 
         ON GENEALOGIA.GEN_IDENTI = PARIENTES.GEN_IDEPAD
   
)

SELECT
   * 
FROM 
   PARIENTES
ORDER BY
   GEN_IDENTI

Aquí hemos pedido la genealogía de la persona con Identificador igual a 10, entonces obtendremos lo siguiente:

RECURSIVO04

Captura 4. Si haces clic en la imagen la verás más grande

Explicación:

Con la instrucción WITH RECURSIVE PARIENTES AS ( … ) hemos creado una tabla virtual, o sea una tabla que solamente existe en la memoria de la computadora, cuyo nombre es PARIENTES y que es recursiva. ¿Qué significa que sea recursiva? que mientras pueda irá ejecutando los comandos que hayamos colocado dentro suyo.

¿Y cuál es la condición de fin?

Siempre que usamos recursividad debemos colocar una condición de fin, o de lo contrario se ejecutará infinitas veces (o mejor dicho, mientras tenga memoria disponible y luego se «colgará»). En este caso nuestra condición de fin lo da el JOIN, mientras una fila tenga algún valor en la columna GEN_IDEPAD continuará la ejecución, cuando ya no tenga valor (o sea, cuando GEN_IDEPAD sea NULL) finalizará porque ya no se cumplicará la condición que habíamos establecido de que GENEALOGIA.GEN_IDENTI = PARIENTES.GEN_IDEPAD.

¿Y por qué se usó UNION ALL?

Porque el primer SELECT es el que nos dice desde donde debemos empezar y en una CTE recursiva siempre hay que usar UNION ALL para agregar las demás filas que necesitamos. El primer SELECT se ejecuta una sola vez, el segundo SELECT se ejecuta muchas veces. Y si te fijas, en el segundo SELECT se usó el nombre de la tabla CTE que estamos creando, que en este caso es PARIENTES. O sea que dentro de la tabla virtual PARIENTES se usó a la tabla virtual PARIENTES y a eso se le llama … RECURSIVIDAD.

En síntesis:

Para crear una CTE recursiva, debemos:

  1. Escribir la palabra RECURSIVE a continuación de la palabra WITH
  2. Escribir un SELECT que determine cual será la primera fila, o sea donde empezará la recursividad
  3. Escribir UNION ALL
  4. Escribir un SELECT que tenga una condición de fin. Esa condición de fin puede colocarse en la cláusula WHERE o en la cláusula JOIN

Una vez que hemos creado a nuestra tabla virtual (o sea, a nuestra tabla CTE) ya la podemos usar como a cualquier otra tabla. Podemos mostrar su contenido (como en el ejemplo de este artículo) o usarla en una subconsulta, en un JOIN, etc.

Conclusión:

La recursividad es una técnica muy poderosa aunque muy poco usada. Debemos pensar en usar recursividad cuando hay una relación directa entre dos filas, lo cual generalmente se da en las tablas que pueden ser autoreferenciadas.

Cuidado especial debemos tomar en asegurarnos de que siempre exista una condición de fin y que a esa condición de fin se llegue alguna vez, de lo contrario a nuestra tabla virtual se le irán agregando filas y más filas, hasta que la computadora se quede sin memoria disponible y en ese momento se «colgará» por falta de memoria.

Hay personas que tienen facilidad para pensar de forma recursiva (como el autor de este blog) y personas a quienes les cuesta mucho pensar así. El no pensar de forma recursiva no es un defecto, todos los algoritmos recursivos también pueden expresarse de otra manera, sin usar recursividad. La ventaja de la recursividad es que en algunos algoritmos (no en todos, en algunos) es muy sencilla de implementar, con pocas líneas se llega al resultado buscado.

Por ejemplo, si quieres obtener los mismos resultados que te muestra el Listado 1. sin usar recursividad desde luego que podrás hacerlo, pero sin dudas que escribirás mucho más. Se notará muchísimo la diferencia cuando los niveles no sean 4 como en nuestro ejemplo sino que sean 12, 20, ó más.

Si aprendemos a usar recursividad entonces tendremos una herramienta más para poder realizar nuestras tareas.

Artículos relacionados:

Stored procedures recursivos

Entendiendo las tablas autoreferenciadas

Usando CTE (Common Table Expression)

Otro ejemplo de CTE: ventas semanales

Usando varias CTE en una vista o en un stored procedure

FOR SELECT y tablas CTE

El índice del blog Firebird21

El foro del blog Firebird21