WITH common_table_expression (Transact-SQL)
指定稱為一般資料表運算式 (CTE) 的暫存具名結果集。這是從簡單查詢衍生而來,定義在單一 SELECT、INSERT、UPDATE 或 DELETE 陳述式的執行範圍內。您也可以在 CREATE VIEW 陳述式中使用這個子句,作為用來定義 SELECT 陳述式的一部分。一般資料表運算式可以包括指向本身的參考。這稱為遞迴一般資料表運算式。
語法
[ WITH <common_table_expression> [ ,...n ] ]
<common_table_expression>::=
expression_name [ ( column_name [ ,...n ] ) ]
AS
( CTE_query_definition )
引數
- expression_name
這是一般資料表運算式的有效識別碼。 expression_name 與相同 WITH <common_table_expression> 子句所定義之任何其他一般資料表運算式的名稱不得相同,但 expression_name 可以與基底資料表或檢視同名。查詢中任何指向 expression_name 的參考都使用一般資料表運算式,而不是基底物件。
- column_name
在一般資料表運算式中,指定資料行名稱。在單一 CTE 定義內,名稱不能重複。指定的資料行名稱數目必須符合 CTE_query_definition 的結果集資料行數目。只有在查詢定義提供了所有結果資料行的個別名稱時,資料行名稱清單才是選擇性的。
CTE_query_definition
指定其結果集擴展一般資料表運算式的 SELECT 陳述式。除了 CTE 不能建立另一個 CTE 之外,CTE_query_definition 的 SELECT 陳述式必須符合建立檢視的相同需求。如需詳細資訊,請參閱「備註」一節和<CREATE VIEW (Transact-SQL)>。如果定義了多個 CTE_query_definition,就必須由下列設定運算子來聯結查詢定義:UNION ALL、UNION、EXCEPT 或 INTERSECT。如需有關如何使用遞迴 CTE 查詢定義的詳細資訊,請參閱下面的「備註」一節和<使用一般資料表運算式的遞迴查詢>。
備註
建立和使用 CTE 的方針
下列方針適用於非遞迴的 CTE。如需適用於遞迴 CTE 的方針,請參閱下面的「定義和使用遞迴 CTE 的方針」。
- CTE 之後必須接著參考部分或所有 CTE 資料行的單一 INSERT、UPDATE 或 DELETE 陳述式。您也可以在 CREATE VIEW 陳述式中,將 CTE 指定為用來定義檢視的 SELECT 陳述式的一部分。
- 您可以在非遞迴的 CTE 中,定義多個 CTE 查詢定義。這些定義必須由下列其中一個設定運算子所組合:UNION ALL、UNION、INTERSECT 或 EXCEPT。
- CTE 可以參考它本身,以及先前在相同 WITH 子句中所定義的 CTE。不允許向前參考。
- 不允許在 CTE 中指定多個 WITH 子句。例如,如果 CTE_query_definition 包含子查詢,這個子查詢就不能包含定義另一個 CTE 的巢狀 WITH 子句。
- 在 CTE_query_definition 中,不能使用下列子句:
- COMPUTE 或 COMPUTE BY
- ORDER BY (除非指定了 TOP 子句)
- INTO
- 含有查詢提示的 OPTION 子句
- FOR XML
- FOR BROWSE
- 當批次中的陳述式使用 CTE 時,在 CTE 之前的陳述式,後面必須接著分號。
- 參考 CTE 的查詢可用來定義資料指標。
- 在 CTE 中,可以參考遠端伺服器的資料表。
- 當執行 CTE 時,任何參考 CTE 的提示都可能如同在查詢中參考檢視的提示,與 CTE 存取基礎資料表時所發現的其他提示衝突。當這個情況發生時,查詢會傳回錯誤。如需詳細資訊,請參閱<檢視解析>。
定義和使用遞迴 CTE 的方針
當定義遞迴 CTE 時,適用下列方針:
- 遞迴 CTE 定義必須包含至少兩個 CTE 查詢定義,錨點成員和遞迴成員各一個。您可以定義多個錨點成員和遞迴成員;不過,所有錨點成員查詢定義都必須放在第一個遞迴成員定義的前面。除非 CTE 查詢定義參考 CTE 本身,否則,它們都是錨點成員。**
- 錨點成員必須由下列其中一個設定運算子所組合:UNION ALL、UNION、INTERSECT 或 EXCEPT。在最後一個錨點成員和第一個遞迴成員之間,以及在組合多個成員時,UNION ALL 是唯一允許使用的設定運算子。
- 錨點和遞迴成員中的資料行數目必須相同。
- 遞迴成員資料行的資料類型必須與錨點成員中對應資料行的資料類型相同。
- 遞迴成員的 FROM 子句只能參考 CTE expression_name 一次。
- 遞迴成員的 CTE_query_definition 不允許使用下列項目:
- SELECT DISTINCT
- GROUP BY
- HAVING
- 純量彙總
- TOP
- LEFT、RIGHT、OUTER JOIN (允許 INNER JOIN)
- 子查詢
- 適用於 CTE_query_definition 內 CTE 之遞迴參考的提示。
當使用遞迴 CTE 時,適用下列方針:
- 遞迴 CTE 能夠傳回的所有資料行都可為 Null,不論參與的 SELECT 陳述式所傳回之資料行的 Null 屬性為何,都是如此。
- 組合不正確的遞迴 CTE 可能會造成無限迴圈。例如,如果遞迴成員查詢定義針對父資料行和子資料行傳回相同的值,就會建立無限迴圈。若要防止無限迴圈,您可以在 INSERT、UPDATE、DELETE 或 SELECT 陳述式的 OPTION 子句中使用 MAXRECURSION 提示以及 0 和 32,767 之間的值,來限制特定陳述式所能使用的遞迴層級數目。這可讓您控制陳述式的執行,直到產生迴圈的程式碼問題解決為止。伺服器範圍預設值是 100。當指定 0 時,不會套用任何限制。每個陳述式只能指定一個 MAXRECURSION 值。如需詳細資訊,請參閱<查詢提示 (Transact-SQL)>。
- 您不能利用包含遞迴一般資料表運算式的檢視來更新資料。
- 資料指標可以利用 CTE 在查詢中定義。CTE 是 select_statement 定義資料指標之結果集的引數。遞迴 CTE 只能使用僅限向前快轉和靜態 (快照集) 資料指標。如果在遞迴 CTE 中指定了另一種資料指標類型,就會將資料指標類型轉換成靜態。
- 在 CTE 中,可以參考遠端伺服器的資料表。如果在 CTE 遞迴成員參考遠端伺服器,便會為每個遠端資料表各建立一項多工緩衝處理,以便在本機重複存取資料表。
範例
A. 建立簡單的一般資料表運算式
下列範例會顯示直接向 Adventure Works Cycles 之每位經理提出報告的員工人數。
USE AdventureWorks;
GO
WITH DirReps(ManagerID, DirectReports) AS
(
SELECT ManagerID, COUNT(*)
FROM HumanResources.Employee AS e
WHERE ManagerID IS NOT NULL
GROUP BY ManagerID
)
SELECT ManagerID, DirectReports
FROM DirReps
ORDER BY ManagerID;
GO
B. 利用一般資料表運算式來限制計數和報告平均值
下列範例會顯示向經理提出報告的平均員工人數。
WITH DirReps (Manager, DirectReports) AS
(
SELECT ManagerID, COUNT(*) AS DirectReports
FROM HumanResources.Employee
GROUP BY ManagerID
)
SELECT AVG(DirectReports) AS [Average Number of Direct Reports]
FROM DirReps
WHERE DirectReports>= 2 ;
GO
C. 重複參考一般資料表運算式
下列範例會顯示每位銷售人員的 SalesOrderHeader
資料表中,銷售訂單的總數和最近的銷售訂單日期。在執行的陳述式中,參考 CTE 兩次:一次是傳回銷售人員所選的資料行,一次是擷取銷售人員經理的類似明細。銷售人員和經理的資料都在單一資料列中傳回。
USE AdventureWorks;
GO
WITH Sales_CTE (SalesPersonID, NumberOfOrders, MaxDate)
AS
(
SELECT SalesPersonID, COUNT(*), MAX(OrderDate)
FROM Sales.SalesOrderHeader
GROUP BY SalesPersonID
)
SELECT E.EmployeeID, OS.NumberOfOrders, OS.MaxDate,
E.ManagerID, OM.NumberOfOrders, OM.MaxDate
FROM HumanResources.Employee AS E
JOIN Sales_CTE AS OS
ON E.EmployeeID = OS.SalesPersonID
LEFT OUTER JOIN Sales_CTE AS OM
ON E.ManagerID = OM.SalesPersonID
ORDER BY E.EmployeeID;
GO
D. 利用遞迴一般資料表運算式來顯示多層級的遞迴
下列範例會顯示經理及向經理提出報告的員工的階層式清單。
USE AdventureWorks;
GO
WITH DirectReports(ManagerID, EmployeeID, EmployeeLevel) AS
(
SELECT ManagerID, EmployeeID, 0 AS EmployeeLevel
FROM HumanResources.Employee
WHERE ManagerID IS NULL
UNION ALL
SELECT e.ManagerID, e.EmployeeID, EmployeeLevel + 1
FROM HumanResources.Employee e
INNER JOIN DirectReports d
ON e.ManagerID = d.EmployeeID
)
SELECT ManagerID, EmployeeID, EmployeeLevel
FROM DirectReports ;
GO
E. 利用遞迴一般資料表運算式來顯示兩個層級的遞迴
下列範例會顯示經理及向經理提出報告的員工。傳回的層級數目只限兩個。
USE AdventureWorks;
GO
WITH DirectReports(ManagerID, EmployeeID, EmployeeLevel) AS
(
SELECT ManagerID, EmployeeID, 0 AS EmployeeLevel
FROM HumanResources.Employee
WHERE ManagerID IS NULL
UNION ALL
SELECT e.ManagerID, e.EmployeeID, EmployeeLevel + 1
FROM HumanResources.Employee e
INNER JOIN DirectReports d
ON e.ManagerID = d.EmployeeID
)
SELECT ManagerID, EmployeeID, EmployeeLevel
FROM DirectReports
WHERE EmployeeLevel <= 2 ;
GO
F. 利用遞迴一般資料表運算式來顯示階層式清單
下列範例是以 C 範例為基礎來建立的,它加入了經理和員工的姓名及其職稱。各個層級會進行縮排,更明顯地強調經理和員工的階層。
USE AdventureWorks;
GO
WITH DirectReports(Name, Title, EmployeeID, EmployeeLevel, Sort)
AS (SELECT CONVERT(varchar(255), c.FirstName + ' ' + c.LastName),
e.Title,
e.EmployeeID,
1,
CONVERT(varchar(255), c.FirstName + ' ' + c.LastName)
FROM HumanResources.Employee AS e
JOIN Person.Contact AS c ON e.ContactID = c.ContactID
WHERE e.ManagerID IS NULL
UNION ALL
SELECT CONVERT(varchar(255), REPLICATE ('| ' , EmployeeLevel) +
c.FirstName + ' ' + c.LastName),
e.Title,
e.EmployeeID,
EmployeeLevel + 1,
CONVERT (varchar(255), RTRIM(Sort) + '| ' + FirstName + ' ' +
LastName)
FROM HumanResources.Employee as e
JOIN Person.Contact AS c ON e.ContactID = c.ContactID
JOIN DirectReports AS d ON e.ManagerID = d.EmployeeID
)
SELECT EmployeeID, Name, Title, EmployeeLevel
FROM DirectReports
ORDER BY Sort;
GO
G. 利用 MAXRECURSION 來取消陳述式
您可以利用 MAXRECURSION
來防止形式不良的遞迴 CTE 進入無限迴圈。下列範例會刻意建立無限迴圈,然後利用 MAXRECURSION
提示,將遞迴層級限制為 2。
USE AdventureWorks;
GO
--Creates an infinite loop
WITH cte (EmployeeID, ManagerID, Title) as
(
SELECT EmployeeID, ManagerID, Title
FROM HumanResources.Employee
WHERE ManagerID IS NOT NULL
UNION ALL
SELECT cte.EmployeeID, cte.ManagerID, cte.Title
FROM cte
JOIN HumanResources.Employee AS e
ON cte.ManagerID = e.EmployeeID
)
--Uses MAXRECURSION to limit the recursive levels to 2
SELECT EmployeeID, ManagerID, Title
FROM cte
OPTION (MAXRECURSION 2);
GO
更正編碼錯誤之後,就不再需要 MAXRECURSION。下列範例會顯示更正的程式碼。
USE AdventureWorks;
GO
WITH cte (EmployeeID, ManagerID, Title)
AS
(
SELECT EmployeeID, ManagerID, Title
FROM HumanResources.Employee
WHERE ManagerID IS NOT NULL
UNION ALL
SELECT e.EmployeeID, e.ManagerID, e.Title
FROM HumanResources.Employee AS e
JOIN cte ON e.ManagerID = cte.EmployeeID
)
SELECT EmployeeID, ManagerID, Title
FROM cte;
GO
H. 利用一般資料表運算式,在 SELECT 陳述式中選擇性地逐步執行遞迴關聯性
下列範例會顯示建立 ProductAssemblyID = 800
的自行車時,所需要之產品組件和元件的階層。
USE AdventureWorks;
GO
WITH Parts(AssemblyID, ComponentID, PerAssemblyQty, EndDate, ComponentLevel) AS
(
SELECT b.ProductAssemblyID, b.ComponentID, b.PerAssemblyQty,
b.EndDate, 0 AS ComponentLevel
FROM Production.BillOfMaterials AS b
WHERE b.ProductAssemblyID = 800
AND b.EndDate IS NULL
UNION ALL
SELECT bom.ProductAssemblyID, bom.ComponentID, p.PerAssemblyQty,
bom.EndDate, ComponentLevel + 1
FROM Production.BillOfMaterials AS bom
INNER JOIN Parts AS p
ON bom.ProductAssemblyID = p.ComponentID
AND bom.EndDate IS NULL
)
SELECT AssemblyID, ComponentID, Name, PerAssemblyQty, EndDate,
ComponentLevel
FROM Parts AS p
INNER JOIN Production.Product AS pr
ON p.ComponentID = pr.ProductID
ORDER BY ComponentLevel, AssemblyID, ComponentID;
GO
I. 在 UPDATE 陳述式中使用遞迴 CTE
下列範例會將直接或間接向 ManagerID 12
提出報告的所有員工之 VacationHours
值更新為 25%。一般資料表運算式會傳回一份階層式員工清單,其中包括直接向 ManagerID 12
提出報告的員工,以及向這些員工提出報告的員工,依此類推。只會修改一般資料表運算式所傳回的資料列。
USE AdventureWorks;
GO
WITH DirectReports(EmployeeID, NewVacationHours, EmployeeLevel)
AS
(SELECT e.EmployeeID, e.VacationHours, 1
FROM HumanResources.Employee AS e
WHERE e.ManagerID = 12
UNION ALL
SELECT e.EmployeeID, e.VacationHours, EmployeeLevel + 1
FROM HumanResources.Employee as e
JOIN DirectReports AS d ON e.ManagerID = d.EmployeeID
)
UPDATE HumanResources.Employee
SET VacationHours = VacationHours * 1.25
FROM HumanResources.Employee AS e
JOIN DirectReports AS d ON e.EmployeeID = d.EmployeeID;
GO
J. 使用多個錨點和遞迴成員
下列範例會利用多個錨點和遞迴成員來傳回指定人員的所有上階。它會建立一份資料表,且會插入值來建立遞迴 CTE 所傳回的家族族譜。
-- Genealogy table
IF OBJECT_ID('Person','U') IS NOT NULL DROP TABLE Person;
GO
CREATE TABLE Person(ID int, Name varchar(30), Mother int, Father int);
GO
INSERT Person VALUES(1, 'Sue', NULL, NULL);
INSERT Person VALUES(2, 'Ed', NULL, NULL);
INSERT Person VALUES(3, 'Emma', 1, 2);
INSERT Person VALUES(4, 'Jack', 1, 2);
INSERT Person VALUES(5, 'Jane', NULL, NULL);
INSERT Person VALUES(6, 'Bonnie', 5, 4);
INSERT Person VALUES(7, 'Bill', 5, 4);
GO
-- Create the recursive CTE to find all of Bonnie's ancestors.
WITH Generation (ID) AS
(
-- First anchor member returns Bonnie's mother.
SELECT Mother
FROM Person
WHERE Name = 'Bonnie'
UNION
-- Second anchor member returns Bonnie's father.
SELECT Father
FROM Person
WHERE Name = 'Bonnie'
UNION ALL
-- First recursive member returns male ancestors of the previous generation.
SELECT Person.Father
FROM Generation, Person
WHERE Generation.ID=Person.ID
UNION ALL
-- Second recursive member returns female ancestors of the previous generation.
SELECT Person.Mother
FROM Generation, Person
WHERE Generation.ID=Person.ID
)
SELECT Person.ID, Person.Name, Person.Mother, Person.Father
FROM Generation, Person
WHERE Generation.ID = Person.ID;
GO
請參閱
參考
CREATE VIEW (Transact-SQL)
DELETE (Transact-SQL)
EXCEPT 和 INTERSECT (Transact-SQL)
INSERT (Transact-SQL)
SELECT (Transact-SQL)
UPDATE (Transact-SQL)