Buscar..


Sintaxis

  • WITH QueryName [(ColumnName, ...)] AS (
    SELECCIONAR ...
    )
    SELECT ... FROM QueryName ...;

  • CON RECURSIVE QueryName [(ColumnName, ...)] AS (
    SELECCIONAR ...
    UNION [TODOS]
    SELECCIONAR ... DE QueryName ...
    )
    SELECT ... FROM QueryName ...;

Observaciones

Documentación oficial: con cláusula.

Una expresión de tabla común es un conjunto de resultados temporal y puede ser el resultado de una sub consulta compleja. Se define utilizando la cláusula WITH. CTE mejora la legibilidad y se crea en la memoria en lugar de en la base de datos TempDB donde se crean la tabla Temp y la variable Table.

Conceptos clave de las expresiones de tabla comunes:

  • Se puede utilizar para dividir consultas complejas, especialmente combinaciones complejas y subconsultas.
  • Es una forma de encapsular una definición de consulta.
  • Persistir solo hasta que se ejecute la siguiente consulta.
  • El uso correcto puede llevar a mejoras tanto en la calidad / mantenibilidad del código como en la velocidad.
  • Se puede usar para hacer referencia a la tabla resultante varias veces en la misma declaración (eliminar la duplicación en SQL).
  • Puede ser un sustituto de una vista cuando no se requiere el uso general de una vista; es decir, no es necesario almacenar la definición en metadatos.
  • Se ejecutará cuando se llame, no cuando esté definido. Si el CTE se usa varias veces en una consulta, se ejecutará varias veces (posiblemente con resultados diferentes).

Consulta temporal

Se comportan de la misma manera que las subconsultas anidadas pero con una sintaxis diferente.

WITH ReadyCars AS (
  SELECT *
  FROM Cars
  WHERE Status = 'READY'
)
SELECT ID, Model, TotalCost
FROM ReadyCars
ORDER BY TotalCost;
CARNÉ DE IDENTIDAD Modelo Coste total
1 Ford F-150 200
2 Ford F-150 230

Sintaxis de subconsulta equivalente

SELECT ID, Model, TotalCost
FROM (
  SELECT *
  FROM Cars
  WHERE Status = 'READY'
) AS ReadyCars
ORDER BY TotalCost

subiendo recursivamente en un árbol

WITH RECURSIVE ManagersOfJonathon AS (
    -- start with this row
    SELECT *
    FROM Employees
    WHERE ID = 4

    UNION ALL

    -- get manager(s) of all previously selected rows
    SELECT Employees.*
    FROM Employees
    JOIN ManagersOfJonathon
        ON Employees.ID = ManagersOfJonathon.ManagerID
)
SELECT * FROM ManagersOfJonathon;
Carné de identidad FName LName Número de teléfono ManagerId DepartmentId
4 Johnathon Herrero 1212121212 2 1
2 Juan Johnson 2468101214 1 1
1 James Herrero 1234567890 NULO 1

generando valores

La mayoría de las bases de datos no tienen una forma nativa de generar una serie de números para uso ad-hoc; sin embargo, las expresiones de tabla comunes pueden usarse con recursión para emular ese tipo de función.

El siguiente ejemplo genera una expresión de tabla común llamada Numbers con una columna i que tiene una fila para los números del 1 al 5:

--Give a table name `Numbers" and a column `i` to hold the numbers
WITH Numbers(i) AS (
    --Starting number/index
    SELECT 1
    --Top-level UNION ALL operator required for recursion
    UNION ALL
    --Iteration expression:
    SELECT i + 1
    --Table expression we first declared used as source for recursion
    FROM Numbers
    --Clause to define the end of the recursion
    WHERE i < 5
)
--Use the generated table expression like a regular table
SELECT i FROM Numbers;
yo
1
2
3
4
5

Este método se puede utilizar con cualquier intervalo de números, así como con otros tipos de datos.

enumeración recursiva de un subárbol

WITH RECURSIVE ManagedByJames(Level, ID, FName, LName) AS (
    -- start with this row
    SELECT 1, ID, FName, LName
    FROM Employees
    WHERE ID = 1

    UNION ALL

    -- get employees that have any of the previously selected rows as manager
    SELECT ManagedByJames.Level + 1,
           Employees.ID,
           Employees.FName,
           Employees.LName
    FROM Employees
    JOIN ManagedByJames
        ON Employees.ManagerID = ManagedByJames.ID

    ORDER BY 1 DESC   -- depth-first search
)
SELECT * FROM ManagedByJames;
Nivel CARNÉ DE IDENTIDAD FName LName
1 1 James Herrero
2 2 Juan Johnson
3 4 Johnathon Herrero
2 3 Miguel Williams

Funcionalidad Oracle CONNECT BY con CTEs recursivas

La funcionalidad CONNECT BY de Oracle proporciona muchas características útiles y no triviales que no están integradas cuando se usan CTE recursivos estándar de SQL. Este ejemplo replica estas características (con algunas adiciones para completar), utilizando la sintaxis de SQL Server. Es más útil para los desarrolladores de Oracle que encuentran muchas características que faltan en sus consultas jerárquicas en otras bases de datos, pero también sirve para mostrar lo que se puede hacer con una consulta jerárquica en general.

  WITH tbl AS (
       SELECT id, name, parent_id
         FROM mytable)
     , tbl_hierarchy AS (
       /* Anchor */
       SELECT 1 AS "LEVEL"
            --, 1 AS CONNECT_BY_ISROOT
            --, 0 AS CONNECT_BY_ISBRANCH
            , CASE WHEN t.id IN (SELECT parent_id FROM tbl) THEN 0 ELSE 1 END AS CONNECT_BY_ISLEAF
            , 0 AS CONNECT_BY_ISCYCLE
            , '/' + CAST(t.id   AS VARCHAR(MAX)) + '/' AS SYS_CONNECT_BY_PATH_id
            , '/' + CAST(t.name AS VARCHAR(MAX)) + '/' AS SYS_CONNECT_BY_PATH_name
            , t.id AS root_id
            , t.*
         FROM tbl t
        WHERE t.parent_id IS NULL                            -- START WITH parent_id IS NULL
       UNION ALL
       /* Recursive */
       SELECT th."LEVEL" + 1 AS "LEVEL"
            --, 0 AS CONNECT_BY_ISROOT
            --, CASE WHEN t.id IN (SELECT parent_id FROM tbl) THEN 1 ELSE 0 END AS CONNECT_BY_ISBRANCH
            , CASE WHEN t.id IN (SELECT parent_id FROM tbl) THEN 0 ELSE 1 END AS CONNECT_BY_ISLEAF
            , CASE WHEN th.SYS_CONNECT_BY_PATH_id LIKE '%/' + CAST(t.id AS VARCHAR(MAX)) + '/%' THEN 1 ELSE 0 END AS CONNECT_BY_ISCYCLE
            , th.SYS_CONNECT_BY_PATH_id   + CAST(t.id   AS VARCHAR(MAX)) + '/' AS SYS_CONNECT_BY_PATH_id
            , th.SYS_CONNECT_BY_PATH_name + CAST(t.name AS VARCHAR(MAX)) + '/' AS SYS_CONNECT_BY_PATH_name
            , th.root_id
            , t.*
         FROM tbl t
              JOIN tbl_hierarchy th ON (th.id = t.parent_id) -- CONNECT BY PRIOR id = parent_id
        WHERE th.CONNECT_BY_ISCYCLE = 0)                     -- NOCYCLE
SELECT th.*
     --, REPLICATE(' ', (th."LEVEL" - 1) * 3) + th.name AS tbl_hierarchy
  FROM tbl_hierarchy th
       JOIN tbl CONNECT_BY_ROOT ON (CONNECT_BY_ROOT.id = th.root_id)
 ORDER BY th.SYS_CONNECT_BY_PATH_name;                       -- ORDER SIBLINGS BY name

CONECTAR POR características demostradas anteriormente, con explicaciones:

  • Cláusulas
    • CONECTAR POR: Especifica la relación que define la jerarquía.
    • COMENZAR CON: Especifica los nodos raíz.
    • ORDEN DE HABLAR POR: Ordena los resultados correctamente.
  • Parámetros
    • NOCYCLE: detiene el procesamiento de una rama cuando se detecta un bucle. Las jerarquías válidas son Gráficos Acíclicos Dirigidos, y las referencias circulares violan esta construcción.
  • Los operadores
    • ANTES: Obtiene datos del padre del nodo.
    • CONNECT_BY_ROOT: Obtiene datos de la raíz del nodo.
  • Pseudocolumnas
    • NIVEL: indica la distancia del nodo desde su raíz.
    • CONNECT_BY_ISLEAF: indica un nodo sin hijos.
    • CONNECT_BY_ISCYCLE: indica un nodo con una referencia circular.
  • Funciones
    • SYS_CONNECT_BY_PATH: devuelve una representación aplanada / concatenada de la ruta al nodo desde su raíz.

Generar fechas recursivamente, extendido para incluir la lista de equipos como ejemplo

DECLARE @DateFrom DATETIME = '2016-06-01 06:00'
DECLARE @DateTo DATETIME = '2016-07-01 06:00'
DECLARE @IntervalDays INT = 7

-- Transition Sequence = Rest & Relax into Day Shift into Night Shift
-- RR (Rest & Relax) = 1
-- DS (Day Shift) = 2
-- NS (Night Shift) = 3

;WITH roster AS
(
   SELECT @DateFrom AS RosterStart, 1 AS TeamA, 2 AS TeamB, 3 AS TeamC
   UNION ALL
   SELECT DATEADD(d, @IntervalDays, RosterStart),
          CASE TeamA WHEN 1 THEN 2 WHEN 2 THEN 3 WHEN 3 THEN 1 END AS TeamA,
          CASE TeamB WHEN 1 THEN 2 WHEN 2 THEN 3 WHEN 3 THEN 1 END AS TeamB,
          CASE TeamC WHEN 1 THEN 2 WHEN 2 THEN 3 WHEN 3 THEN 1 END AS TeamC
   FROM roster WHERE RosterStart < DATEADD(d, -@IntervalDays, @DateTo)
)

SELECT RosterStart,
       ISNULL(LEAD(RosterStart) OVER (ORDER BY RosterStart), RosterStart + @IntervalDays) AS RosterEnd,
       CASE TeamA WHEN 1 THEN 'RR' WHEN 2 THEN 'DS' WHEN 3 THEN 'NS' END AS TeamA,
       CASE TeamB WHEN 1 THEN 'RR' WHEN 2 THEN 'DS' WHEN 3 THEN 'NS' END AS TeamB,
       CASE TeamC WHEN 1 THEN 'RR' WHEN 2 THEN 'DS' WHEN 3 THEN 'NS' END AS TeamC
FROM roster

Resultado

Por ejemplo, para la semana 1, el equipo está en R&R, el equipo B está en el turno de día y el equipo C está en el turno de noche.

introduzca la descripción de la imagen aquí

Refactorizar una consulta para usar expresiones de tabla comunes

Supongamos que queremos obtener todas las categorías de productos con ventas totales superiores a 20.

Aquí hay una consulta sin expresiones de tabla comunes:

SELECT category.description, sum(product.price) as total_sales
FROM sale
LEFT JOIN product on sale.product_id = product.id
LEFT JOIN category on product.category_id = category.id
GROUP BY category.id, category.description
HAVING sum(product.price) > 20

Y una consulta equivalente usando expresiones de tabla comunes:

WITH all_sales AS (
  SELECT product.price, category.id as category_id, category.description as category_description
  FROM sale
  LEFT JOIN product on sale.product_id = product.id
  LEFT JOIN category on product.category_id = category.id
)
, sales_by_category AS (
  SELECT category_description, sum(price) as total_sales
  FROM all_sales
  GROUP BY category_id, category_description
)
SELECT * from sales_by_category WHERE total_sales > 20

Ejemplo de un SQL complejo con expresión de tabla común

Supongamos que queremos consultar los "productos más baratos" de las "categorías principales".

Aquí hay un ejemplo de consulta usando expresiones de tabla comunes

-- all_sales: just a simple SELECT with all the needed JOINS
WITH all_sales AS (
  SELECT
  product.price as product_price,
  category.id as category_id,
  category.description as category_description
  FROM sale
  LEFT JOIN product on sale.product_id = product.id
  LEFT JOIN category on product.category_id = category.id
)
-- Group by category
, sales_by_category AS (
  SELECT category_id, category_description,
  sum(product_price) as total_sales
  FROM all_sales
  GROUP BY category_id, category_description
)
-- Filtering total_sales > 20
, top_categories AS (
  SELECT * from sales_by_category WHERE total_sales > 20
)
-- all_products: just a simple SELECT with all the needed JOINS
, all_products AS (
  SELECT
  product.id as product_id,
  product.description as product_description,
  product.price as product_price,
  category.id as category_id,
  category.description as category_description
  FROM product
  LEFT JOIN category on product.category_id = category.id
)
-- Order by product price
, cheapest_products AS (
  SELECT * from all_products
  ORDER by product_price ASC
)
-- Simple inner join 
, cheapest_products_from_top_categories AS (
  SELECT product_description, product_price
  FROM cheapest_products
  INNER JOIN top_categories ON cheapest_products.category_id = top_categories.category_id
)
--The main SELECT
SELECT * from cheapest_products_from_top_categories


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow