sql >> Base de Datos >  >> RDS >> Sqlserver

Guía para CTE en SQL Server

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:

  1. CTESales
  2. 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.