sql >> Databasteknik >  >> RDS >> Sqlserver

Guide för CTE i SQL Server

Det vanliga tabelluttrycket aka CTE i SQL Server ger en tillfällig resultatuppsättning i T-SQL. Du kan referera till det i en SQL Select-, SQL Insert-, SQL Delete- eller SQL Update-sats.

Alternativet är tillgängligt från SQL Server 2005 och framåt, och hjälper utvecklarna att skriva komplexa och långa frågor som involverar många JOINs, aggregering och datafiltrering. Vanligtvis använder utvecklare underfrågor för att skriva T-SQL-koder, och SQL Server lagrar dessa CTE i minnet tillfälligt tills exekveringen av frågan är klar. När frågan är klar tas den bort från minnet.

CTE i SQL Server:Syntax

WITH <common_table_expression> ([column names])
AS
(
   <query_definition>
)
<operation>
  • Den använder ett CTE-namn för att hänvisa till den för att utföra satserna Select, Insert, Update, Delete eller Merge.
  • Kolumnnamn är kommaseparerade. De bör matcha kolumnerna som definieras i frågedefinitionen.
  • Frågedefinitionen involverar urvalssatserna från en enda tabell eller sammanfogar mellan flera tabeller.
  • Du kan hänvisa till CTE-uttryckets namn för att hämta resultaten.

Till exempel använder följande grundläggande CTE-fråga följande delar:

  • Common Table Expression name – SalesCustomerData
  • Kolumnlista – [Kund-ID],[Förnamn],[Efternamn],[Företagsnamn],[E-postadress],[Telefon]
  • Frågedefinitionen innehåller en select-sats som hämtar data från tabellen [SalesLT].[Customer]
  • Den sista delen använder select-satsen på CTE-uttrycket och filtrerar poster med where-satsen.
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

I ett annat exempel beräknar vi den genomsnittliga totala försäljningen från CTE. Frågedefinitionen inkluderar GROUP BY-satsen. Senare använder vi funktionen AVG() för att beräkna medelvärdet.

WITH Salesdata ([SalesOrderID],[Total])
AS(
SELECT [SalesOrderID]
         ,count(*) AS total
          FROM [SalesLT].[SalesOrderHeader]
        GROUP BY [SalesOrderID]
)
SELECT avg(total) FROM salesdata

Du kan också använda CTE för att infoga data i SQL-tabellen. CTE-frågedefinitionen inkluderar de nödvändiga data som du kan hämta från befintliga tabeller med hjälp av joins. Senare, fråga CTE för att infoga data i måltabellen.

Här använder vi SELECT INTO-satsen för att skapa en ny tabell med namnet [CTETest] från utdata från CTE select-satsen.

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

Du kan också ange en befintlig tabell som matchar kolumnerna med infogade data.

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

Du kan också uppdatera eller ta bort poster i SQL-tabellen med det vanliga tabelluttrycket. Följande frågor använder DELETE- och UPDATE-satser med CTE.

Uppdatera uttalande i CTE

WITH Salesdata ([SalesOrderID],[Freight])
AS(
SELECT [SalesOrderID]
         ,[Freight]
          FROM [SalesLT].[SalesOrderHeader]
        )
UPDATE SalesData SET [Freight]=100.00 WHERE [SalesOrderID]=71774
Go

Ta bort uttalande i 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

Flera CTE

Du kan deklarera flera CTE:er i T-SQL-skriptet och använda join-operationerna på dem. För multipel CTE använder T-SQL ett kommatecken som avgränsare.

I följande fråga har vi två CTE:

  1. CTESales
  2. CTESalesDescription

Senare, i select-satsen, hämtar vi resultat med INNER JOIN på båda 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]

Rekursiva vanliga tabelluttryck

Den rekursiva CTE körs i en upprepad procedurloop tills villkoret uppfylls. Följande T-SQL-exempel använder en ID-räknare och väljer poster tills WHERE-villkoret är uppfyllt.

Declare @ID int =1;
;with RecursiveCTE as  
   (  
      SELECT @ID as ID
        UNION ALL  
      SELECT  ID+ 1
  FROM  RecursiveCTE  
  WHERE ID <5
    )  
 
SELECT * FROM RecursiveCTE

En annan användning av rekursiv CTE i SQL Server är att visa hierarkisk data. Antag att vi har en anställd tabell, och den har register för alla anställda, deras avdelningar och deras chefers ID.

--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 måste vi generera personalhierarkidata. Vi kan använda rekursiv CTE med UNION ALL i select-satsen.

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 returnerar uppgifterna på anställd nivå som visas nedan.

Viktiga punkter angående vanliga tabelluttryck

  • Vi kan inte återanvända CTE. Dess omfattning är begränsad till de yttre SELECT-, INSERT-, UPDATE- eller MERGE-satserna.
  • Du kan använda flera CTES; de bör dock använda operatorerna UNION ALL, UNION, INTERSECT eller EXCERPT.
  • Vi kan definiera flera CTE-frågedefinitioner i den icke-rekursiva CTE.
  • Vi kan inte använda ORDER BY (utan TOP), INTO, OPTIONS-satsen med frågetips och FOR Browse i CTE-frågedefinitionen.

Till exempel använder skriptet nedan ORDER BY-satsen utan en TOP-sats.

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

Det ger följande fel:

  • Vi kan inte skapa ett index på CTE.
  • De definierade kolumnnamnen i CTE bör matcha kolumnerna som returneras i select-satsen.

CTE har inte kolumnen [Phone] i koden nedan medan select-satsen returnerar sitt värde. Därför får du det markerade felmeddelandet.

  • Om du har flera satser i T-SQL-skriptet, bör den föregående satsen före CTE avslutas med semikolon.

Till exempel innehåller den första select-satsen inget semikolon. Därför får du ett felaktigt syntaxfel i CTE-skriptet.

Skriptet fungerar bra om vi avslutar den första select-satsen med semikolonoperatorn.

  • Vi kan inte använda en dubblettkolumn i select-satsen om vi inte deklarerar kolumnnamnet externt.

Till exempel anger följande CTE-definition dubblettkolumnen [Telefon]. Det returnerar ett fel.

Men om du definierar externa kolumner kommer det inte att orsaka fel. Det krävs när du behöver en enstaka kolumn flera gånger i utdata för olika beräkningar.

Viktigt:Common Table Expressions (CTE) är inte en ersättning för temporära tabeller eller tabellvariabler.

  • Templariska tabeller skapas i TempDB, och vi kan definiera indexbegränsningar som liknar en vanlig tabell. Vi kan inte referera till temptabellen flera gånger under en session
  • Tabellvariabler finns också i TempDB och fungerar som variabler som existerar under batchkörningen. Vi kan inte definiera ett index på tabellvariablerna.
  • CTE är avsedd för en enda referens, och vi kan inte definiera indexet på det. Den finns i minnet och släpps på efter referens.

Slutsats

Common Table Expressions (CTE) låter utvecklarna skriva ren och effektiv kod. I allmänhet kan du använda CTE där du inte behöver flera referenser som en temporär tabell, och vi utforskade olika scenarier för SELECT, INSERT, UPDATE, DETELTE-sats och rekursiva CTE:er.

Med hjälp av moderna verktyg, som SQL Complete SSMS Add-in, blir hanteringen av CTE:er ännu enklare. Tillägget kan föreslå CTEs i farten, vilket gör uppgifterna som involverar CTE mycket enklare. Se även Microsofts dokumentation för mer information om CTE.


  1. Hur kan jag bli av med dessa kommentarer i en MySQL-dump?

  2. Har mysql motsvarigheten till Oracles analytiska funktioner?

  3. ORA-00904:ogiltig identifierare

  4. Överträdelse av integritetsbegränsning:1452 Kan inte lägga till eller uppdatera en underordnad rad: