Expresión de tabla común: la cláusula WITH

Una expresión de tabla común o CTE (Common Table Expression) es un conjunto de resultados temporal al que puedes hacer referencia en consultas posteriores. Para escribir una expresión de tabla común se usa la cláusula WITH.

MySQL 8.4
-- Ejemplo de uso de la cláusula WITH
WITH Aeroflot_trips AS
    (SELECT TRIP.* FROM Company
        INNER JOIN Trip ON Trip.company = Company.id WHERE name = 'Aeroflot')

SELECT plane, COUNT(plane) AS amount FROM Aeroflot_trips GROUP BY plane;

Una expresión con WITH se considera «temporal» porque su resultado no se guarda de forma permanente en ningún sitio del esquema de la base de datos, sino que actúa como una vista temporal que solo existe mientras se ejecuta la consulta, es decir, solo está disponible durante la ejecución de las sentencias SELECT, INSERT, UPDATE, DELETE o MERGE. Solo es válida dentro de la consulta a la que pertenece, lo que permite mejorar la estructura de la consulta sin ensuciar el espacio de nombres global.

Sintaxis de la cláusula WITH

MySQL 8.4
WITH nombre_cte [(columna [, ...])] AS (subconsulta)
    [, nombre_cte [(columna [, ...])] AS (subconsulta)] ...
  • [ ]parte opcional
  • ...se puede repetir
nombre_cte

El nombre de la expresión de tabla común. Si hay varias expresiones, se separan con comas.

columna

Los nombres de las columnas de la expresión de tabla, separados por comas. Si no los indicas, se toman de la subconsulta.

Opcional
subconsulta

La consulta cuyo resultado puedes usar en otras partes de la consulta SQL mediante el nombre nombre_cte.

Subconsulta

Ejemplos de consultas

  1. Creamos la expresión de tabla Aeroflot_trips, que contiene todos los vuelos realizados por la aerolínea «Aeroflot».
MySQL 8.4
WITH Aeroflot_trips AS
    (SELECT plane, town_from, town_to FROM Company
        INNER JOIN Trip ON Trip.company = Company.id WHERE name = 'Aeroflot')

SELECT * FROM Aeroflot_trips;
planetown_fromtown_to
IL-86MoscowRostov
IL-86RostovMoscow
  1. Del mismo modo, creamos la expresión de tabla Aeroflot_trips, pero con las columnas renombradas.
MySQL 8.4
WITH Aeroflot_trips (aeroflot_plane, town_from, town_to) AS
    (SELECT plane, town_from, town_to FROM Company
        INNER JOIN Trip ON Trip.company = Company.id WHERE name = 'Aeroflot')

SELECT * FROM Aeroflot_trips;
aeroflot_planetown_fromtown_to
IL-86MoscowRostov
IL-86RostovMoscow
  1. Con la cláusula WITH definimos varias expresiones de tabla.
MySQL 8.4
WITH Aeroflot_trips AS
    (SELECT TRIP.* FROM Company
        INNER JOIN Trip ON Trip.company = Company.id WHERE name = 'Aeroflot'),
    Don_avia_trips AS
    (SELECT TRIP.* FROM Company
        INNER JOIN Trip ON Trip.company = Company.id WHERE name = 'Don_avia')

SELECT * FROM Don_avia_trips UNION SELECT * FROM  Aeroflot_trips;
idcompanyplanetown_fromtown_totime_outtime_in
11811TU-134RostovMoscow1900-01-01T06:12:00.000Z1900-01-01T08:01:00.000Z
11821TU-134MoscowRostov1900-01-01T12:35:00.000Z1900-01-01T14:30:00.000Z
11871TU-134RostovMoscow1900-01-01T15:42:00.000Z1900-01-01T17:39:00.000Z
11881TU-134MoscowRostov1900-01-01T22:50:00.000Z1900-01-02T00:48:00.000Z
11951TU-154RostovMoscow1900-01-01T23:30:00.000Z1900-01-02T01:11:00.000Z
11961TU-154MoscowRostov1900-01-01T04:00:00.000Z1900-01-01T05:45:00.000Z
11452IL-86MoscowRostov1900-01-01T09:35:00.000Z1900-01-01T11:23:00.000Z
11462IL-86RostovMoscow1900-01-01T17:55:00.000Z1900-01-01T20:01:00.000Z

Recursión en las CTE

Las CTE también se pueden usar para escribir consultas recursivas, que permiten procesar los datos de forma iterativa, por ejemplo, para trabajar con estructuras jerárquicas como «jefe — subordinado».

Sintaxis de una CTE recursiva

Una CTE recursiva consta de dos partes separadas por el operador UNION ALL:

  • El conjunto de datos inicial, que no contiene referencias recursivas.
  • La parte recursiva: una consulta que hace referencia a la propia CTE para continuar la recursión.
MySQL 8.4
WITH RECURSIVE nombre_cte [(columna [, ...])] AS (
    -- Conjunto de datos inicial
    SELECT expresion [, ...]
    FROM nombre_tabla
    [WHERE condicion]

    UNION ALL

    -- Parte recursiva
    SELECT expresion [, ...]
    FROM nombre_cte
    INNER JOIN nombre_tabla ON condicion_combinacion
    [WHERE condicion]
)

SELECT * FROM nombre_cte;
  • [ ]parte opcional
  • ...se puede repetir
nombre_cte

El nombre de la expresión de tabla. La parte recursiva hace referencia a él para continuar la recursión.

columna

Los nombres de las columnas de la expresión de tabla. Sin la lista, se toman los nombres de la parte inicial.

Opcional
expresion

Las columnas y los cálculos que selecciona cada parte. Las dos partes devuelven el mismo número de columnas. La parte inicial fija los tipos de las columnas, y los valores de la parte recursiva deben ser compatibles con ellos. En la parte recursiva suele haber cálculos como level + 1.

nombre_tabla

La tabla con los datos de partida, por ejemplo Employees.

condicion_combinacion

Cómo se relacionan las filas de la tabla con las filas encontradas en el paso anterior, por ejemplo e.managerId = s.id.

condicion

Filtra filas: en la parte inicial, con qué filas empezar; en la recursiva, qué filas añadir en el siguiente paso.

Opcional

Ejemplo: jerarquía de jefes y subordinados

Veamos la tabla Employees, que contiene los identificadores de los empleados y de sus jefes:

idnamemanagerId
1John Smith<NULL>
2Michael Johnson1
3Robert Williams1
4James Brown2
5David Jones2
6Richard Davis3

Hay que encontrar a todos los subordinados de John Smith (id=1) en todos los niveles de la jerarquía.

MySQL 8.4
WITH RECURSIVE Subordinates AS (
    -- Conjunto de datos inicial
    SELECT id, name, managerId
    FROM Employees
    WHERE managerId = 1

    UNION ALL

    -- Parte recursiva: subordinados de los subordinados
    SELECT e.id, e.name, e.managerId
    FROM Employees e
    INNER JOIN Subordinates s ON e.managerId = s.id
)

SELECT * FROM Subordinates;
idnamemanagerId
2Michael Johnson1
3Robert Williams1
4James Brown2
5David Jones2
6Richard Davis3

Pasos de ejecución de una CTE recursiva

  1. Conjunto de datos inicial: se seleccionan todos los empleados con managerId=1 (subordinados directos de John Smith).
  2. Parte recursiva: para cada empleado seleccionado en el conjunto inicial, se seleccionan sus subordinados (aquellos cuyo managerId es igual al id del empleado seleccionado).
  3. Unión: los resultados del conjunto inicial y de la parte recursiva se unen con UNION ALL.
  4. Recursión: el proceso se repite para cada nuevo conjunto de subordinados hasta recorrer todos los niveles de la jerarquía.

Conclusión

Las expresiones de tabla comunes se añadieron a SQL para simplificar consultas largas y complejas, sobre todo las que tienen muchas subconsultas. Su objetivo principal es que las consultas sean más legibles, más fáciles de escribir y de mantener. Esto se consigue encapsulando las consultas grandes y complejas en expresiones con nombre que después se usan en la consulta principal.