SQL
Expresiones de mesa comunes
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.
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