Common Table Expression aka CTE v SQL Server poskytuje dočasnou sadu výsledků v T-SQL. Můžete na něj odkazovat v příkazu SQL Select, SQL Insert, SQL Delete nebo SQL Update.
Tato možnost je k dispozici od SQL Server 2005 a novější a pomáhá vývojářům psát složité a dlouhé dotazy zahrnující mnoho spojení JOIN, agregaci a filtrování dat. Vývojáři obvykle používají poddotazy pro psaní kódů T-SQL a SQL Server dočasně ukládá tyto CTE do paměti, dokud neskončí provádění dotazu. Jakmile je dotaz dokončen, je odstraněn z paměti.
CTE v SQL Server:Syntaxe
WITH <common_table_expression> ([column names])
AS
(
<query_definition>
)
<operation>
- Pro provádění příkazů Select, Insert, Update, Delete nebo Merge používá název CTE.
- Názvy sloupců jsou odděleny čárkami. Měly by odpovídat sloupcům definovaným v definici dotazu.
- Definice dotazu zahrnuje příkazy select z jedné tabulky nebo spojení mezi více tabulkami.
- Pro získání výsledků můžete použít název výrazu CTE.
Například následující základní dotaz CTE používá následující části:
- Název běžného výrazu tabulky – SalesCustomerData
- Seznam sloupců – [CustomerID],[FirstName],[LastName],[CompanyName],[EmailAddress],[Phone]
- Definice dotazu zahrnuje příkaz select, který získává data z tabulky [SalesLT].[Customer]
- Poslední část používá příkaz select u výrazu CTE a filtruje záznamy pomocí klauzule 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
V dalším příkladu vypočítáme průměrné celkové tržby z CTE. Definice dotazu obsahuje klauzuli GROUP BY. Později použijeme funkci AVG() pro výpočet průměrné hodnoty.
WITH Salesdata ([SalesOrderID],[Total])
AS(
SELECT [SalesOrderID]
,count(*) AS total
FROM [SalesLT].[SalesOrderHeader]
GROUP BY [SalesOrderID]
)
SELECT avg(total) FROM salesdata
CTE můžete také použít k vložení dat do SQL tabulky. Definice dotazu CTE obsahuje požadovaná data, která můžete načíst z existujících tabulek pomocí spojení. Později požádejte CTE o vložení dat do cílové tabulky.
Zde používáme příkaz SELECT INTO k vytvoření nové tabulky s názvem [CTETest] z výstupu příkazu 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
Můžete také určit existující tabulku, která odpovídá sloupcům s vloženými daty.
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
Záznamy v tabulce SQL můžete aktualizovat nebo mazat také pomocí běžného tabulkového výrazu. Následující dotazy používají příkazy DELETE a UPDATE s CTE.
Aktualizovat prohlášení v CTE
WITH Salesdata ([SalesOrderID],[Freight])
AS(
SELECT [SalesOrderID]
,[Freight]
FROM [SalesLT].[SalesOrderHeader]
)
UPDATE SalesData SET [Freight]=100.00 WHERE [SalesOrderID]=71774
Go
Smazat výpis v 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íce CTE
Ve skriptu T-SQL můžete deklarovat více CTE a používat na nich operace spojení. Pro více CTE používá T-SQL jako oddělovač čárku.
V následujícím dotazu máme dva CTE:
- CTESales
- CTESalesDescription
Později, v příkazu select, získáme výsledky pomocí INNER JOIN na obou 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]
Rekurzivní běžné tabulkové výrazy
Rekurzivní CTE běží v opakované procedurální smyčce, dokud podmínka nevyhoví. Následující příklad T-SQL používá čítač ID a vybírá záznamy, dokud není splněna podmínka 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
Dalším využitím rekurzivního CTE v SQL Server je zobrazení hierarchických dat. Předpokládejme, že máme zaměstnance a obsahuje záznamy pro všechny zaměstnance, jejich oddělení a ID jejich manažerů.
--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);
Nyní musíme vygenerovat data hierarchie zaměstnanců. Můžeme použít rekurzivní CTE s UNION ALL v příkazu 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 vrátí podrobnosti o úrovni zaměstnance, jak je uvedeno níže.
Důležité body týkající se běžných tabulkových výrazů
- CTE nemůžeme znovu použít. Jeho rozsah je omezen na vnější příkazy SELECT, INSERT, UPDATE nebo MERGE.
- Můžete použít více CTES; měli by však používat operátory UNION ALL, UNION, INTERSECT nebo EXCERPT.
- V nerekurzivním CTE můžeme definovat více definic dotazů CTE.
- V definici dotazu CTE nemůžeme použít klauzuli ORDER BY (bez TOP), INTO, OPTIONS s nápovědou k dotazu a FOR BROWSE.
Například níže uvedený skript používá klauzuli ORDER BY bez klauzule 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
Hlásí to následující chybu:
- Nemůžeme vytvořit index na CTE.
- Názvy definovaných sloupců v CTE by se měly shodovat se sloupci vrácenými v příkazu select.
CTE nemá v níže uvedeném kódu sloupec [Phone], zatímco příkaz select vrací jeho hodnotu. Proto se zobrazí zvýrazněná chybová zpráva.
- Pokud máte ve skriptu T-SQL více příkazů, měl by předchozí příkaz před CTE končit středníkem.
Například první příkaz select neobsahuje středník. Proto se zobrazí nesprávná syntaktická chyba ve skriptu CTE.
Skript funguje dobře, pokud ukončíme první příkaz select pomocí operátoru středníku.
- Nemůžeme použít duplicitní sloupec v příkazu select, pokud externě nedeklarujeme název sloupce.
Například následující definice CTE určuje duplicitní sloupec [Telefon]. Vrací chybu.
Pokud však definujete externí sloupce, nezpůsobí to chyby. Je vyžadováno, když potřebujete jeden sloupec vícekrát ve výstupu pro různé výpočty.
Důležité:Common Table Expressions (CTE) nenahrazují dočasné tabulky nebo proměnné tabulky.
- Tabulky Temp se vytvářejí v TempDB a můžeme definovat omezení indexu podobná běžné tabulce. Nemůžeme odkazovat na dočasnou tabulku vícekrát během relace
- Proměnné tabulky také existují v TempDB a fungují jako proměnné, které existují během dávkového provádění. Nemůžeme definovat index pro proměnné tabulky.
- CTE je pro jediný referenční účel a nemůžeme na něm definovat index. Existuje v paměti a po vytvoření odkazu je vynechán.
Závěr
Common Table Expressions (CTE) umožňují vývojářům psát čistý a efektivní kód. Obecně platí, že CTE můžete použít tam, kde nepotřebujete více odkazů, jako je dočasná tabulka, a prozkoumali jsme různé scénáře pro příkazy SELECT, INSERT, UPDATE, DETELTE a rekurzivní CTE.
S pomocí moderních nástrojů, jako je SQL Complete SSMS Add-in, je manipulace s CTE ještě jednodušší. Doplněk může navrhovat CTE za běhu, takže úkoly týkající se CTE jsou mnohem jednodušší. Další podrobnosti o CTE naleznete také v dokumentaci společnosti Microsoft.