Sqlserver
 sql >> база данни >  >> RDS >> Sqlserver

Ръководство за CTE в SQL Server

Общият табличен израз известен още като 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:

  1. CTESales
  2. 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.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. SQL Server DATEPART() срещу DATENAME() – Каква е разликата?

  2. tsql връща таблица от функция или процедура за съхранение

  3. Проверете дали таблицата съществува и ако не съществува, създайте я в SQL Server 2008

  4. SQL Server безшумно съкращава varchar в съхранените процедури

  5. Как да създадете таблица с помощта на GUI в SQL Server - SQL Server / T-SQL урок, част 37