Общият табличен израз известен още като CTE в SQL Server предоставя временен набор от резултати в T-SQL. Можете да се обърнете към него в рамките на оператор SQL Select, SQL Insert, SQL Delete или SQL Update.
Опцията е налична от SQL Server 2005 нататък, като помага на разработчиците да пишат сложни и дълги заявки, включващи много JOIN, агрегиране и филтриране на данни. Обикновено разработчиците използват подзаявки за писане на T-SQL кодове, а SQL Server съхранява тези CTE в паметта временно, докато изпълнението на заявката приключи. След като заявката приключи, тя се премахва от паметта.
CTE в SQL Server:Синтаксис
WITH <common_table_expression> ([column names])
AS
(
<query_definition>
)
<operation>
- Използва име на CTE, за да се позовава на него за изпълнение на операторите Select, Insert, Update, Delete или Merge.
- Имената на колоните са разделени със запетая. Те трябва да съвпадат с колоните, дефинирани в дефиницията на заявката.
- Дефиницията на заявката включва изрази за избор от една таблица или връзки между множество таблици.
- Можете да се обърнете към името на CTE израза за извличане на резултатите.
Например следната основна CTE заявка използва следните части:
- Име на общ израз на таблица – SalesCustomerData
- Списък с колони – [CustomerID], [FirstName], [LastName], [CompanyName], [EmailAddress], [Phone]
- Дефиницията на заявката включва оператор select, който получава данни от таблицата [SalesLT].[Customer]
- Последната част използва оператора select на CTE израза и филтрира записи с помощта на клаузата 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
В друг пример изчисляваме средните общи продажби от CTE. Дефиницията на заявката включва клаузата GROUP BY. По-късно използваме функцията AVG() за изчисляване на средната стойност.
WITH Salesdata ([SalesOrderID],[Total])
AS(
SELECT [SalesOrderID]
,count(*) AS total
FROM [SalesLT].[SalesOrderHeader]
GROUP BY [SalesOrderID]
)
SELECT avg(total) FROM salesdata
Можете също да използвате CTE, за да вмъкнете данни в SQL таблицата. Дефиницията на CTE заявката включва необходимите данни, които можете да извлечете от съществуващи таблици с помощта на обединения. По-късно поискайте CTE за вмъкване на данни в целевата таблица.
Тук използваме оператора SELECT INTO, за да създадем нова таблица с име [CTETest] от изхода на оператора 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
Можете също да посочите съществуваща таблица, която съответства на колоните с вмъкнатите данни.
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
Можете да актуализирате или изтривате записи в SQL таблицата, като използвате и израза за обща таблица. Следните заявки използват изрази DELETE и UPDATE с CTE.
Изявление за актуализация в CTE
WITH Salesdata ([SalesOrderID],[Freight])
AS(
SELECT [SalesOrderID]
,[Freight]
FROM [SalesLT].[SalesOrderHeader]
)
UPDATE SalesData SET [Freight]=100.00 WHERE [SalesOrderID]=71774
Go
Изтриване на изявление в 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
Множество CTE
Можете да декларирате множество CTE в T-SQL скрипта и да използвате операциите за присъединяване върху тях. За множествения CTE T-SQL използва запетая като разделител.
В следната заявка имаме два CTE:
- CTESales
- CTESalesDescription
По-късно, в оператора select, извличаме резултати с помощта на INNER JOIN и на двата 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]
Рекурсивни изрази за обща таблица
Рекурсивният CTE работи в повтарящ се процедурен цикъл, докато условието не удовлетвори. Следващият пример за T-SQL използва брояч на ID и избира записи, докато условието 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
Друго използване на рекурсивния CTE в SQL Server е за показване на йерархични данни. Да приемем, че имаме наслужител таблица и има записи за всички служители, техните отдели и идентификационните номера на техните мениджъри.
--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);
Сега трябва да генерираме йерархичните данни на служителите. Можем да използваме рекурсивна CTE с UNION ALL в оператора 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;
CTE връща подробностите за нивото на служителите, както е показано по-долу.
Важни точки по отношение на общите таблични изрази
- Не можем да използваме повторно CTE. Неговият обхват е ограничен до външните изрази SELECT, INSERT, UPDATE или MERGE.
- Можете да използвате множество CTES; те обаче трябва да използват оператори UNION ALL, UNION, INTERSECT или EXCERPT.
- Можем да дефинираме множество дефиниции на CTE заявка в нерекурсивния CTE.
- Не можем да използваме клауза ORDER BY (без TOP), INTO, OPTIONS със съвети за заявка и FOR BROWSE в дефиницията на CTE заявката.
Например, скриптът по-долу използва клаузата ORDER BY без клауза 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
Дава следната грешка:
- Не можем да създадем индекс на CTE.
- Дефинираните имена на колони в CTE трябва да съвпадат с колоните, върнати в оператора select.
CTE няма колоната [Phone] в кода по-долу, докато операторът select връща стойността си. Следователно получавате маркираното съобщение за грешка.
- Ако имате няколко израза в T-SQL скрипта, предишният израз преди CTE трябва да приключи с точка и запетая.
Например, първият оператор за избор не включва точка и запетая. Следователно получавате неправилна синтактична грешка в CTE скрипта.
Скриптът работи добре, ако прекратим първия оператор select с помощта на оператора точка и запетая.
- Не можем да използваме дублирана колона в оператора select, ако не декларираме името на колоната външно.
Например, следната дефиниция на CTE определя дублиращата се колона [Телефон]. Връща грешка.
Въпреки това, ако дефинирате външни колони, това няма да причини грешки. Той е необходим, когато имате нужда от една колона няколко пъти в изхода за различни изчисления.
Важно:Общите таблични изрази (CTE) не са заместител на временните таблици или табличните променливи.
- Времените таблици се създават в TempDB и можем да дефинираме индексни ограничения, подобни на обикновена таблица. Не можем да препращаме временната таблица няколко пъти в една сесия
- Променливите на таблицата също съществуват в TempDB и действат като променливи, които съществуват по време на пакетното изпълнение. Не можем да дефинираме индекс за променливите в таблицата.
- CTE е с една референтна цел и не можем да дефинираме индекса върху него. Той съществува в паметта и се изпуска след като се направи препратка.
Заключение
Общите таблични изрази (CTE) позволяват на разработчиците да пишат чист и ефективен код. Като цяло можете да използвате CTE, където не се нуждаете от множество препратки, като временна таблица, и ние проучихме различни сценарии за SELECT, INSERT, UPDATE, DETELTE и рекурсивни CTE.
С помощта на съвременни инструменти, като SQL Complete SSMS Add-in, обработката на CTE става още по-лесна. Добавката може да предлага CTE в движение, като по този начин прави задачите, включващи CTE, много по-лесни. Също така вижте документацията на Microsoft за повече подробности относно CTE.