轉載自:https://blog.csdn.net/qq_36617521/article/details/55256382 ,
創建和使用 CTE 的指南
下列指南應用於非遞歸 CTE。有關適用於遞歸 CTE 的指南,請參閱后面的“定義和使用遞歸 CTE 的指南”。
- CTE 之后必須跟隨引用部分或全部 CTE 列的 SELECT、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 本身時除外。
- 定位點成員必須與以下集合運算符之一結合使用: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:
- 無論參與的 SELECT 語句返回的列的為空性如何,遞歸 CTE 返回的全部列都可以為空。
- 如果遞歸 CTE 組合不正確,可能會導致無限循環。例如,如果遞歸成員查詢定義對父列和子列返回相同的值,則會造成無限循環。可以使用 MAXRECURSION 提示以及在 INSERT、UPDATE、DELETE 或 SELECT 語句的 OPTION 子句中的一個 0 到 32,767 之間的值,來限制特定語句所允許的遞歸級數,以防止出現無限循環。這樣就能夠在解決產生循環的代碼問題之前控制語句的執行。服務器范圍內的默認值是 100。如果指定 0,則沒有限制。每一個語句只能指定一個 MAXRECURSION 值。有關詳細信息,請參閱查詢提示 (Transact-SQL)。
- 不能使用包含遞歸公用表表達式的視圖來更新數據。
- 可以使用 CTE 在查詢上定義游標。遞歸 CTE 只允許使用快速只進游標和靜態(快照)游標。如果在遞歸 CTE 中指定了其他游標類型,則該類型將轉換為靜態游標類型。
- 可以在 CTE 中引用遠程服務器中的表。如果在 CTE 的遞歸成員中引用了遠程服務器,那么將為每個遠程表創建一個假脫機,這樣就可以在本地反復訪問這些表。
參數
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 查詢定義的詳細信息,請參閱下列“備注”部分和使用公用表表達式的遞歸查詢。
示例
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 |
|
使用遞歸公用表表達式顯示遞歸的多個級別。
以下示例顯示經理以及向經理報告的雇員的層次列表。
復制代碼 |
|
|---|---|
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 提示來將遞歸級別限制為兩個。
復制代碼 |
|
|---|---|
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 |
|
使用多個定位點和遞歸成員
以下示例使用多個定位點和遞歸成員來返回指定的人的所有祖先。創建了一個表,並在表中插入值,以建立由遞歸 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 |
|
