De Algemene tabeluitdrukking ook bekend als CTE in SQL Server biedt een tijdelijke resultaatset in T-SQL. U kunt ernaar verwijzen in een SQL Select-, SQL Insert-, SQL Delete- of SQL Update-instructie.
De optie is beschikbaar vanaf SQL Server 2005 en helpt de ontwikkelaars bij het schrijven van complexe en lange query's met veel JOIN's, aggregatie en gegevensfiltering. Gewoonlijk gebruiken ontwikkelaars subquery's voor het schrijven van T-SQL-codes, en SQL Server slaat deze CTE tijdelijk op in het geheugen totdat de uitvoering van de query is voltooid. Zodra de query is voltooid, wordt deze uit het geheugen verwijderd.
CTE in SQL Server:syntaxis
WITH <common_table_expression> ([column names])
AS
(
<query_definition>
)
<operation>
- Het gebruikt een CTE-naam om ernaar te verwijzen voor het uitvoeren van de instructies Selecteren, Invoegen, Bijwerken, Verwijderen of Samenvoegen.
- Kolomnamen worden door komma's gescheiden. Ze moeten overeenkomen met de kolommen die zijn gedefinieerd in de querydefinitie.
- De querydefinitie omvat de select-statements uit een enkele tabel of joins tussen meerdere tabellen.
- U kunt de naam van de CTE-expressie raadplegen om de resultaten op te halen.
De volgende basis-CTE-query gebruikt bijvoorbeeld de volgende onderdelen:
- Algemene naam voor tabeluitdrukkingen – SalesCustomerData
- Kolommenlijst – [Klant-ID],[Voornaam],[Achternaam],[Bedrijfsnaam],[E-mailadres],[Telefoon]
- De querydefinitie bevat een select-instructie die gegevens haalt uit de tabel [SalesLT].[Customer]
- Het laatste deel gebruikt de select-instructie op de CTE-expressie en filtert records met de where-clausule.
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
In een ander voorbeeld berekenen we de gemiddelde totale omzet uit de CTE. De querydefinitie bevat de GROUP BY-component. Later gebruiken we de functie AVG() om de gemiddelde waarde te berekenen.
WITH Salesdata ([SalesOrderID],[Total])
AS(
SELECT [SalesOrderID]
,count(*) AS total
FROM [SalesLT].[SalesOrderHeader]
GROUP BY [SalesOrderID]
)
SELECT avg(total) FROM salesdata
U kunt CTE ook gebruiken om gegevens in de SQL-tabel in te voegen. De CTE-querydefinitie bevat de vereiste gegevens die u kunt ophalen uit bestaande tabellen met behulp van joins. Vraag later CTE om gegevens in de doeltabel in te voegen.
Hier gebruiken we de SELECT INTO-instructie om een nieuwe tabel met de naam [CTETest] te maken op basis van de uitvoer van de CTE select-instructie.
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
U kunt ook een bestaande tabel specificeren die overeenkomt met de kolommen met de ingevoegde gegevens.
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
U kunt records in de SQL-tabel ook bijwerken of verwijderen met behulp van de algemene tabelexpressie. De volgende zoekopdrachten gebruiken DELETE- en UPDATE-instructies met CTE.
Verklaring in CTE bijwerken
WITH Salesdata ([SalesOrderID],[Freight])
AS(
SELECT [SalesOrderID]
,[Freight]
FROM [SalesLT].[SalesOrderHeader]
)
UPDATE SalesData SET [Freight]=100.00 WHERE [SalesOrderID]=71774
Go
Verwijder verklaring in 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
Meerdere CTE's
U kunt meerdere CTE's declareren in het T-SQL-script en de join-bewerkingen daarop gebruiken. Voor de meervoudige CTE gebruikt T-SQL een komma als scheidingsteken.
In de volgende zoekopdracht hebben we twee CTE's:
- CTESales
- CTESalesDescription
Later, in de select-instructie, halen we resultaten op met INNER JOIN op beide CTE's.
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]
Recursieve algemene tabeluitdrukkingen
De recursieve CTE loopt in een herhaalde procedurele lus totdat de voorwaarde voldoet. Het volgende T-SQL-voorbeeld gebruikt een ID-teller en selecteert records totdat aan de WHERE-voorwaarde is voldaan.
Declare @ID int =1;
;with RecursiveCTE as
(
SELECT @ID as ID
UNION ALL
SELECT ID+ 1
FROM RecursiveCTE
WHERE ID <5
)
SELECT * FROM RecursiveCTE
Een ander gebruik van recursieve CTE in SQL Server is om hiërarchische gegevens weer te geven. Stel dat we een werknemer hebben tabel, en het heeft records voor alle werknemers, hun afdelingen en de ID's van hun managers.
--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);
Nu moeten we de gegevens over de werknemershiërarchie genereren. We kunnen recursieve CTE gebruiken met UNION ALL in de select-instructie.
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;
De CTE retourneert de details op werknemersniveau zoals hieronder getoond.
Belangrijke punten met betrekking tot algemene tabeluitdrukkingen
- We kunnen de CTE niet opnieuw gebruiken. Het bereik is beperkt tot de buitenste SELECT-, INSERT-, UPDATE- of MERGE-instructies.
- U kunt meerdere CTES gebruiken; ze moeten echter de operators UNION ALL, UNION, INTERSECT of EXCERPT gebruiken.
- We kunnen meerdere CTE-querydefinities definiëren in de niet-recursieve CTE.
- We kunnen de ORDER BY (zonder TOP), INTO, OPTIONS-clausules met queryhints en FOR BROWSE niet gebruiken in de CTE-querydefinitie.
Het onderstaande script gebruikt bijvoorbeeld de ORDER BY-clausule zonder een TOP-clausule.
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
Het geeft de volgende foutmelding:
- We kunnen geen index maken op de CTE.
- De gedefinieerde kolomnamen in CTE moeten overeenkomen met de kolommen die worden geretourneerd in de select-instructie.
De CTE heeft de [Telefoon]-kolom niet in de onderstaande code, terwijl de select-instructie de waarde ervan retourneert. Daarom krijgt u de gemarkeerde foutmelding.
- Als u meerdere instructies in het T-SQL-script hebt, moet de vorige instructie vóór CTE eindigen met een puntkomma.
De eerste select-instructie bevat bijvoorbeeld geen puntkomma. Daarom krijg je een onjuiste syntaxisfout in het CTE-script.
Het script werkt prima als we de eerste select-opdracht beëindigen met de puntkomma-operator.
- We kunnen geen dubbele kolom gebruiken in de select-instructie als we de kolomnaam niet extern declareren.
De volgende CTE-definitie specificeert bijvoorbeeld de dubbele kolom [Telefoon]. Er wordt een fout geretourneerd.
Als u echter externe kolommen definieert, veroorzaakt dit geen fouten. Het is vereist wanneer u een enkele kolom meerdere keren nodig heeft in de uitvoer voor verschillende berekeningen.
Belangrijk:Common Table Expressions (CTE) zijn geen vervanging voor de tijdelijke tabellen of tabelvariabelen.
- Temp-tabellen worden gemaakt in de TempDB en we kunnen indexbeperkingen definiëren die vergelijkbaar zijn met een gewone tabel. We kunnen niet meerdere keren in een sessie naar de tijdelijke tabel verwijzen
- Tabelvariabelen bestaan ook in de TempDB en gedragen zich als variabelen die bestaan tijdens de batchuitvoering. We kunnen geen index definiëren voor de tabelvariabelen.
- CTE is voor een enkel referentiedoel en we kunnen de index erop niet definiëren. Het bestaat in het geheugen en wordt verwijderd nadat er naar verwezen is.
Conclusie
Met de Common Table Expressions (CTE) kunnen ontwikkelaars schone en effectieve code schrijven. Over het algemeen kunt u CTE gebruiken waar u niet meerdere verwijzingen nodig hebt, zoals een tijdelijke tabel, en we hebben verschillende scenario's onderzocht voor SELECT, INSERT, UPDATE, DETELTE-instructies en recursieve CTE's.
Met behulp van moderne tools, zoals SQL Complete SSMS Add-in, wordt het afhandelen van CTE's nog eenvoudiger. De invoegtoepassing kan direct CTE's voorstellen, waardoor de taken met betrekking tot CTE veel eenvoudiger worden. Raadpleeg ook de Microsoft-documentatie voor meer details over de CTE.