A Expressão de tabela comum também conhecido como CTE no SQL Server fornece um conjunto de resultados temporário em T-SQL. Você pode consultá-lo em uma instrução SQL Select, SQL Insert, SQL Delete ou SQL Update.
A opção está disponível a partir do SQL Server 2005, ajudando os desenvolvedores a escrever consultas complexas e longas envolvendo muitos JOINs, agregação e filtragem de dados. Normalmente, os desenvolvedores usam subconsultas para escrever códigos T-SQL e o SQL Server armazena esses CTEs na memória temporariamente até que a execução da consulta termine. Quando a consulta é concluída, ela é removida da memória.
CTE no SQL Server:Sintaxe
WITH <common_table_expression> ([column names])
AS
(
<query_definition>
)
<operation>
- Ele usa um nome CTE para se referir a ele para executar as instruções Select, Insert, Update, Delete ou Merge.
- Os nomes das colunas são separados por vírgulas. Eles devem corresponder às colunas definidas na definição da consulta.
- A definição da consulta envolve as instruções select de uma única tabela ou junções entre várias tabelas.
- Você pode consultar o nome da expressão CTE para recuperar os resultados.
Por exemplo, a seguinte consulta CTE básica usa as seguintes partes:
- Nome da expressão de tabela comum – SalesCustomerData
- Lista de colunas – [CustomerID],[FirstName],[LastName],[CompanyName],[EmailAddress],[Phone]
- A definição da consulta inclui uma instrução select que obtém dados da tabela [SalesLT].[Customer]
- A última parte usa a instrução select na expressão CTE e filtra os registros usando a 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
Em outro exemplo, calculamos a média total de vendas do CTE. A definição de consulta inclui a cláusula GROUP BY. Posteriormente, usamos a função AVG() para calcular o valor médio.
WITH Salesdata ([SalesOrderID],[Total])
AS(
SELECT [SalesOrderID]
,count(*) AS total
FROM [SalesLT].[SalesOrderHeader]
GROUP BY [SalesOrderID]
)
SELECT avg(total) FROM salesdata
Você também pode usar CTE para inserir dados na tabela SQL. A definição de consulta CTE inclui os dados necessários que você pode buscar de tabelas existentes usando junções. Posteriormente, consulte o CTE para inserir dados na tabela de destino.
Aqui usamos a instrução SELECT INTO para criar uma nova tabela chamada [CTETest] a partir da saída da instrução CTE select.
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
Você também pode especificar uma tabela existente que corresponda às colunas com os dados inseridos.
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
Você também pode atualizar ou excluir registros na tabela SQL usando a expressão de tabela comum. As consultas a seguir usam instruções DELETE e UPDATE com CTE.
Atualizar Declaração no CTE
WITH Salesdata ([SalesOrderID],[Freight])
AS(
SELECT [SalesOrderID]
,[Freight]
FROM [SalesLT].[SalesOrderHeader]
)
UPDATE SalesData SET [Freight]=100.00 WHERE [SalesOrderID]=71774
Go
Excluir extrato no 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
Vários CTEs
Você pode declarar vários CTEs no script T-SQL e usar as operações de junção neles. Para o CTE múltiplo, o T-SQL usa uma vírgula como separador.
Na consulta a seguir, temos dois CTEs:
- CTESVendas
- CTESDescrição de vendas
Mais tarde, na instrução select, recuperamos os resultados usando INNER JOIN em ambos os CTEs.
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]
Expressões de tabela comuns recursivas
O CTE recursivo é executado em um loop procedural repetido até que a condição seja satisfeita. O exemplo de T-SQL a seguir usa um contador de ID e seleciona registros até que a condição WHERE seja satisfeita.
Declare @ID int =1;
;with RecursiveCTE as
(
SELECT @ID as ID
UNION ALL
SELECT ID+ 1
FROM RecursiveCTE
WHERE ID <5
)
SELECT * FROM RecursiveCTE
Outro uso da CTE recursiva no SQL Server é exibir dados hierárquicos. Suponha que temos um funcionário e tem registros para todos os funcionários, seus departamentos e IDs de seus 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);
Agora, precisamos gerar os dados de hierarquia de funcionários. Podemos usar CTE recursiva com UNION ALL na instrução select.
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;
O CTE retorna os detalhes do nível do funcionário, conforme mostrado abaixo.
Pontos importantes sobre expressões de tabela comuns
- Não podemos reutilizar o CTE. Seu escopo é limitado às instruções externas SELECT, INSERT, UPDATE ou MERGE.
- Você pode usar vários CTES; no entanto, eles devem usar os operadores UNION ALL, UNION, INTERSECT ou EXCERPT.
- Podemos definir várias definições de consulta CTE no CTE não recursivo.
- Não podemos usar a cláusula ORDER BY (sem TOP), INTO, OPTIONS com dicas de consulta e FOR BROWSE na definição de consulta CTE.
Por exemplo, o script abaixo usa a cláusula ORDER BY sem uma 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
Dá o seguinte erro:
- Não podemos criar um índice no CTE.
- Os nomes de coluna definidos no CTE devem corresponder às colunas retornadas na instrução select.
O CTE não possui a coluna [Phone] no código abaixo enquanto a instrução select retorna seu valor. Portanto, você recebe a mensagem de erro realçada.
- Se você tiver várias instruções no script T-SQL, a instrução anterior antes de CTE deve terminar usando um ponto e vírgula.
Por exemplo, a primeira instrução select não inclui um ponto e vírgula. Portanto, você recebe um erro de sintaxe incorreto no roteiro CTE.
O script funciona bem se terminarmos a primeira instrução select usando o operador ponto-e-vírgula.
- Não podemos usar uma coluna duplicada na instrução select se não declararmos o nome da coluna externamente.
Por exemplo, a definição de CTE a seguir especifica a coluna duplicada [Phone]. Ele retorna um erro.
No entanto, se você definir colunas externas, isso não causará erros. É necessário quando você precisa de uma única coluna várias vezes na saída para cálculos diferentes.
Importante:Expressões de Tabela Comuns (CTE) não substituem as tabelas temporárias ou variáveis de tabela.
- As tabelas temporárias são criadas no TempDB e podemos definir restrições de índice semelhantes a uma tabela regular. Não podemos fazer referência à tabela temporária várias vezes em uma sessão
- As variáveis de tabela também existem no TempDB e atuam como variáveis que existem durante a execução em lote. Não podemos definir um índice nas variáveis da tabela.
- CTE é para um único propósito de referência e não podemos definir o índice nele. Ele existe na memória e é descartado após a referência ser feita.
Conclusão
As Expressões de Tabela Comum (CTE) permitem que os desenvolvedores escrevam código limpo e eficaz. Em geral, você pode usar o CTE onde não precisar de várias referências, como uma tabela temporária, e exploramos vários cenários para instruções SELECT, INSERT, UPDATE, DETELTE e CTEs recursivos.
Com a ajuda de ferramentas modernas, como o SQL Complete SSMS Add-in, o manuseio de CTEs fica ainda mais fácil. O add-in pode sugerir CTEs em tempo real, tornando as tarefas que envolvem CTE muito mais simples. Além disso, consulte a documentação da Microsoft para obter mais detalhes sobre o CTE.