La expresión de tabla común también conocido como CTE en SQL Server proporciona un conjunto de resultados temporal en T-SQL. Puede hacer referencia a él dentro de una instrucción SQL Select, SQL Insert, SQL Delete o SQL Update.
La opción está disponible desde SQL Server 2005 en adelante, lo que ayuda a los desarrolladores a escribir consultas largas y complejas que involucran muchas JOIN, agregación y filtrado de datos. Por lo general, los desarrolladores usan subconsultas para escribir códigos T-SQL y SQL Server almacena estos CTE en la memoria temporalmente hasta que finaliza la ejecución de la consulta. Una vez finalizada la consulta, se elimina de la memoria.
CTE en SQL Server:Sintaxis
WITH <common_table_expression> ([column names])
AS
(
<query_definition>
)
<operation>
- Utiliza un nombre CTE para referirse a él para realizar las declaraciones Seleccionar, Insertar, Actualizar, Eliminar o Fusionar.
- Los nombres de las columnas están separados por comas. Deben coincidir con las columnas definidas en la definición de consulta.
- La definición de consulta implica las declaraciones de selección de una sola tabla o combinaciones entre varias tablas.
- Puede consultar el nombre de la expresión CTE para recuperar los resultados.
Por ejemplo, la siguiente consulta CTE básica utiliza las siguientes partes:
- Nombre de expresión de tabla común:SalesCustomerData
- Lista de columnas:[ID del cliente], [Nombre], [Apellido], [Nombre de la empresa], [Dirección de correo electrónico], [Teléfono]
- La definición de la consulta incluye una instrucción select que obtiene datos de la tabla [SalesLT].[Customer]
- La última parte usa la instrucción select en la expresión CTE y filtra los registros usando la cláusula where.
WITH SalesCustomerdata ([CustomerID],[FirstName],[LastName],[CompanyName],[EmailAddress],[Phone])
AS(
SELECT [CustomerID]
,[FirstName]
,[LastName]
,[CompanyName]
,[EmailAddress]
,[Phone]
FROM [SalesLT].[Customer]
)
SELECT * FROM SalesCustomerdata where Firstname like 'Raj%'
ORDER BY CustomerID desc
En otro ejemplo, calculamos las ventas totales promedio del CTE. La definición de consulta incluye la cláusula GROUP BY. Luego, usamos la función AVG() para calcular el valor promedio.
WITH Salesdata ([SalesOrderID],[Total])
AS(
SELECT [SalesOrderID]
,count(*) AS total
FROM [SalesLT].[SalesOrderHeader]
GROUP BY [SalesOrderID]
)
SELECT avg(total) FROM salesdata
También puede usar CTE para insertar datos en la tabla SQL. La definición de consulta de CTE incluye los datos necesarios que puede obtener de las tablas existentes mediante combinaciones. Más tarde, consulte CTE para insertar datos en la tabla de destino.
Aquí usamos la declaración SELECT INTO para crear una nueva tabla llamada [CTETest] a partir de la salida de la declaración de selección CTE.
WITH CTEDataInsert
AS
(
SELECT
p.[ProductID]
,p.[Name]
,pm.[Name] AS [ProductModel]
,pmx.[Culture]
,pd.[Description]
FROM [SalesLT].[Product] p
INNER JOIN [SalesLT].[ProductModel] pm
ON p.[ProductModelID] = pm.[ProductModelID]
INNER JOIN [SalesLT].[ProductModelProductDescription] pmx
ON pm.[ProductModelID] = pmx.[ProductModelID]
INNER JOIN [SalesLT].[ProductDescription] pd
ON pmx.[ProductDescriptionID] = pd.[ProductDescriptionID]
)
SELECT * INTO CTETest FROM CTEDataInsert
GO
También puede especificar una tabla existente que coincida con las columnas con los datos insertados.
WITH CTEDataInsert
AS
(
SELECT
p.[ProductID]
,p.[Name]
,pm.[Name] AS [ProductModel]
,pmx.[Culture]
,pd.[Description]
FROM [SalesLT].[Product] p
INNER JOIN [SalesLT].[ProductModel] pm
ON p.[ProductModelID] = pm.[ProductModelID]
INNER JOIN [SalesLT].[ProductModelProductDescription] pmx
ON pm.[ProductModelID] = pmx.[ProductModelID]
INNER JOIN [SalesLT].[ProductDescription] pd
ON pmx.[ProductDescriptionID] = pd.[ProductDescriptionID]
)
INSERT into CTETest select * FROM CTEDataInsert
GO
También puede actualizar o eliminar registros en la tabla SQL utilizando la expresión de tabla común. Las siguientes consultas utilizan sentencias DELETE y UPDATE con CTE.
Declaración de actualización en CTE
WITH Salesdata ([SalesOrderID],[Freight])
AS(
SELECT [SalesOrderID]
,[Freight]
FROM [SalesLT].[SalesOrderHeader]
)
UPDATE SalesData SET [Freight]=100.00 WHERE [SalesOrderID]=71774
Go
Eliminar Declaración en CTE
WITH Salesdata ([SalesOrderID],[Freight])
AS(
SELECT [SalesOrderID]
,[Freight]
FROM [SalesLT].[SalesOrderHeader]
)
delete SalesData WHERE [SalesOrderID]=71774
GO
SELECT * FROM [SalesLT].[SalesOrderHeader] WHERE SalesOrderID=71774
Múltiples CTE
Puede declarar varios CTE en el script T-SQL y usar las operaciones de combinación en ellos. Para el CTE múltiple, T-SQL usa una coma como separador.
En la siguiente consulta, tenemos dos CTE:
- CTESales
- CTESDescripción de ventas
Más tarde, en la declaración de selección, recuperamos los resultados usando INNER JOIN en ambos CTE.
WITH CTESales
AS
(
SELECT
p.[ProductID]
,p.[Name]
,pm.[Name] AS [ProductModel]
,pmx.[Culture]
,pmx.[ProductDescriptionID]
FROM [SalesLT].[Product] p
INNER JOIN [SalesLT].[ProductModel] pm
ON p.[ProductModelID] = pm.[ProductModelID]
INNER JOIN [SalesLT].[ProductModelProductDescription] pmx
ON pm.[ProductModelID] = pmx.[ProductModelID]
INNER JOIN [SalesLT].[ProductDescription] pd
ON pmx.[ProductDescriptionID] = pd.[ProductDescriptionID]
),CTESalesDescription
AS (
SELECT description AS describe,[ProductDescriptionID]
from [SalesLT].[ProductDescription]
)
SELECT productid, [Name],[ProductModel],describe
FROM CTESales
INNER JOIN CTESalesDescription
ON
CTESales.[ProductDescriptionID] = CTESalesDescription.[ProductDescriptionID]
Expresiones de tabla comunes recursivas
El CTE recursivo se ejecuta en un bucle de procedimiento repetido hasta que se cumple la condición. El siguiente ejemplo de T-SQL utiliza un contador de ID y selecciona registros hasta que se cumple la condición WHERE.
Declare @ID int =1;
;with RecursiveCTE as
(
SELECT @ID as ID
UNION ALL
SELECT ID+ 1
FROM RecursiveCTE
WHERE ID <5
)
SELECT * FROM RecursiveCTE
Otro uso de CTE recursivo en SQL Server es mostrar datos jerárquicos. Supongamos que tenemos un empleado y tiene registros para todos los empleados, sus departamentos y las identificaciones de sus gerentes.
--Script Reference: Microsoft Docs
CREATE TABLE dbo.MyEmployees
(
EmployeeID SMALLINT NOT NULL,
FirstName NVARCHAR(30) NOT NULL,
LastName NVARCHAR(40) NOT NULL,
Title NVARCHAR(50) NOT NULL,
DeptID SMALLINT NOT NULL,
ManagerID INT NULL,
CONSTRAINT PK_EmployeeID PRIMARY KEY CLUSTERED (EmployeeID ASC)
);
INSERT INTO dbo.MyEmployees VALUES
(1, N'Ken', N'Sánchez', N'Chief Executive Officer',16,NULL)
,(273, N'Brian', N'Welcker', N'Vice President of Sales',3,1)
,(274, N'Stephen', N'Jiang', N'North American Sales Manager',3,273)
,(275, N'Michael', N'Blythe', N'Sales Representative',3,274)
,(276, N'Linda', N'Mitchell', N'Sales Representative',3,274)
,(285, N'Syed', N'Abbas', N'Pacific Sales Manager',3,273)
,(286, N'Lynn', N'Tsoflias', N'Sales Representative',3,285)
,(16, N'David',N'Bradley', N'Marketing Manager', 4, 273)
,(23, N'Mary', N'Gibson', N'Marketing Specialist', 4, 16);
Ahora, necesitamos generar los datos de la jerarquía de empleados. Podemos usar CTE recursivo con UNION ALL en la declaración de selección.
WITH DirectReports(Name, Title, EmployeeID, EmployeeLevel, Sort)
AS (SELECT CONVERT(VARCHAR(255), e.FirstName + ' ' + e.LastName),
e.Title,
e.EmployeeID,
1,
CONVERT(VARCHAR(255), e.FirstName + ' ' + e.LastName)
FROM dbo.MyEmployees AS e
WHERE e.ManagerID IS NULL
UNION ALL
SELECT CONVERT(VARCHAR(255), REPLICATE ('| ' , EmployeeLevel) +
e.FirstName + ' ' + e.LastName),
e.Title,
e.EmployeeID,
EmployeeLevel + 1,
CONVERT (VARCHAR(255), RTRIM(Sort) + '| ' + FirstName + ' ' +
LastName)
FROM dbo.MyEmployees AS e
JOIN DirectReports AS d ON e.ManagerID = d.EmployeeID
)
SELECT EmployeeID, Name, Title, EmployeeLevel
FROM DirectReports
ORDER BY Sort;
El CTE devuelve los detalles del nivel de empleado como se muestra a continuación.
Puntos importantes sobre las expresiones de tabla comunes
- No podemos reutilizar el CTE. Su alcance está limitado a las declaraciones externas SELECT, INSERT, UPDATE o MERGE.
- Puede usar múltiples CTES; sin embargo, deben usar los operadores UNION ALL, UNION, INTERSECT o EXCERPT.
- Podemos definir múltiples definiciones de consulta CTE en el CTE no recursivo.
- No podemos usar la cláusula ORDER BY (sin TOP), INTO, OPTIONS con sugerencias de consulta y FOR BROWSE en la definición de consulta CTE.
Por ejemplo, el siguiente script usa la cláusula ORDER BY sin una cláusula TOP.
WITH CTEDataInsert
AS
(
SELECT
p.[ProductID]
,p.[Name]
,pm.[Name] AS [ProductModel]
,pmx.[Culture]
,pd.[Description]
FROM [SalesLT].[Product] p
INNER JOIN [SalesLT].[ProductModel] pm
ON p.[ProductModelID] = pm.[ProductModelID]
INNER JOIN [SalesLT].[ProductModelProductDescription] pmx
ON pm.[ProductModelID] = pmx.[ProductModelID]
INNER JOIN [SalesLT].[ProductDescription] pd
ON pmx.[ProductDescriptionID] = pd.[ProductDescriptionID]
ORDER BY productid
)
select * FROM CTEDataInsert
GO
Da el siguiente error:
- No podemos crear un índice en el CTE.
- Los nombres de columna definidos en CTE deben coincidir con las columnas devueltas en la declaración de selección.
El CTE no tiene la columna [Teléfono] en el código siguiente, mientras que la declaración de selección devuelve su valor. Por lo tanto, recibe el mensaje de error resaltado.
- Si tiene varias declaraciones en el script T-SQL, la declaración anterior antes de CTE debe terminar con un punto y coma.
Por ejemplo, la primera declaración de selección no incluye un punto y coma. Por lo tanto, obtiene un error de sintaxis incorrecta en el guión CTE.
El script funciona bien si terminamos la primera declaración de selección usando el operador de punto y coma.
- No podemos usar una columna duplicada en la declaración de selección si no declaramos el nombre de la columna externamente.
Por ejemplo, la siguiente definición de CTE especifica la columna duplicada [Teléfono]. Devuelve un error.
Sin embargo, si define columnas externas, no causará errores. Se requiere cuando necesita una sola columna varias veces en la salida para diferentes cálculos.
Importante:las expresiones de tabla común (CTE) no reemplazan las tablas temporales ni las variables de tabla.
- Las tablas temporales se crean en TempDB y podemos definir restricciones de índice similares a una tabla normal. No podemos hacer referencia a la tabla temporal varias veces en una sesión
- Las variables de tabla también existen en TempDB y actúan como variables que existen durante la ejecución por lotes. No podemos definir un índice en las variables de la tabla.
- CTE tiene un único propósito de referencia y no podemos definir el índice en él. Existe en la memoria y se deja caer después de que se hace la referencia.
Conclusión
Las expresiones de tabla comunes (CTE) permiten a los desarrolladores escribir código limpio y efectivo. En general, puede usar CTE donde no necesita múltiples referencias como una tabla temporal, y exploramos varios escenarios para SELECCIONAR, INSERTAR, ACTUALIZAR, declaración DETELTE y CTE recursivos.
Con la ayuda de herramientas modernas, como el complemento SQL Complete SSMS, el manejo de CTE se vuelve aún más fácil. El complemento puede sugerir CTE sobre la marcha, lo que hace que las tareas relacionadas con CTE sean mucho más sencillas. Además, consulte la documentación de Microsoft para obtener más detalles sobre el CTE.