Skip to content

Commit 1a204fb

Browse files
committed
Restore recursive CTE article (50045)
1 parent 35bec1b commit 1a204fb

File tree

3 files changed

+240
-2
lines changed

3 files changed

+240
-2
lines changed
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
---
2+
title: "Recursive Queries Using Common Table Expressions"
3+
description: "Learn how recursive common table expressions (CTE) work."
4+
author: rwestMSFT
5+
ms.author: randolphwest
6+
ms.date: 07/14/2025
7+
ms.service: sql
8+
ms.subservice: t-sql
9+
ms.topic: reference
10+
ms.custom:
11+
- ignite-2024
12+
helpviewer_keywords:
13+
- "WITH common_table_expression clause recursive"
14+
- "recursive CTEs"
15+
- "hierarchical queries [SQL Server], WITH common_table_expression recursive"
16+
- "recursive CTEs [SQL Server]"
17+
- "recursive queries [SQL Server]"
18+
- "common table expressions"
19+
- "MAXRECURSION hint"
20+
- "recursive clauses [SQL Server], WITH common_table_expression"
21+
dev_langs:
22+
- "TSQL"
23+
monikerRange: ">=aps-pdw-2016 || =azuresqldb-current || =azure-sqldw-latest || >=sql-server-2016 || >=sql-server-linux-2017 || =azuresqldb-mi-current || =fabric"
24+
---
25+
# Recursive queries using common table expressions (Transact-SQL)
26+
27+
[!INCLUDE [sql-asdb-asdbmi-asa-pdw-fabricse-fabricdw-fabricsqldb](../../includes/applies-to-version/sql-asdb-asdbmi-asa-pdw-fabricse-fabricdw-fabricsqldb.md)]
28+
29+
A common table expression (CTE) provides the significant advantage of being able to reference itself, thus creating a recursive CTE. A recursive CTE is one in which an initial CTE is repeatedly executed to return subsets of data until the complete result set is obtained.
30+
31+
A query is referred to as a recursive query when it references a recursive CTE. Returning hierarchical data is a common use of recursive queries. For example, displaying employees in an organizational chart, or data in a bill of materials scenario in which a parent product has one or more components and those components might have subcomponents, or might be components, of other parents.
32+
33+
A recursive CTE can greatly simplify the code required to run a recursive query within a `SELECT`, `INSERT`, `UPDATE`, `DELETE`, or `CREATE VIEW` statement. In earlier versions of SQL Server, a recursive query usually requires using temporary tables, cursors, and logic to control the flow of the recursive steps. For more information about common table expressions, see [WITH common_table_expression](with-common-table-expression-transact-sql.md).
34+
35+
## Structure of a recursive CTE
36+
37+
The structure of the recursive CTE in Transact-SQL is similar to recursive routines in other programming languages. Although a recursive routine in other languages returns a scalar value, a recursive CTE can return multiple rows.
38+
39+
A recursive CTE consists of three elements:
40+
41+
1. Invocation of the routine.
42+
43+
The first invocation of the recursive CTE consists of one or more CTE query definitions joined by `UNION ALL`, `UNION`, `EXCEPT`, or `INTERSECT` operators. Because these query definitions form the base result set of the CTE structure, they're referred to as anchor members.
44+
45+
CTE query definitions are considered anchor members unless they reference the CTE itself. All anchor-member query definitions must be positioned before the first recursive member definition, and a `UNION ALL` operator must be used to join the last anchor member with the first recursive member.
46+
47+
1. Recursive invocation of the routine.
48+
49+
The recursive invocation includes one or more CTE query definitions joined by `UNION ALL` operators that reference the CTE itself. These query definitions are referred to as recursive members.
50+
51+
1. Termination check.
52+
53+
The termination check is implicit; recursion stops when no rows are returned from the previous invocation.
54+
55+
> [!NOTE]
56+
> An incorrectly composed recursive CTE might cause an infinite loop. For example, if the recursive member query definition returns the same values for both the parent and child columns, an infinite loop is created. When testing the results of a recursive query, you can limit the number of recursion levels allowed for a specific statement by using the `MAXRECURSION` hint and a value between 0 and 32,767 in the `OPTION` clause of the `INSERT`, `UPDATE`, `DELETE`, or `SELECT` statement. For more information, see [Query hints](hints-transact-sql-query.md) and [WITH common_table_expression](with-common-table-expression-transact-sql.md).
57+
58+
## Pseudocode and semantics
59+
60+
The recursive CTE structure must contain at least one anchor member and one recursive member. The following pseudocode shows the components of a simple recursive CTE that contains a single anchor member and single recursive member.
61+
62+
```syntaxsql
63+
WITH cte_name ( column_name [ ,...n ] )
64+
AS
65+
(
66+
CTE_query_definition -- Anchor member is defined.
67+
UNION ALL
68+
CTE_query_definition -- Recursive member is defined referencing cte_name.
69+
)
70+
71+
-- Statement using the CTE
72+
SELECT *
73+
FROM cte_name
74+
```
75+
76+
The semantics of the recursive execution is as follows:
77+
78+
1. Split the CTE expression into anchor and recursive members.
79+
1. Run the anchor members creating the first invocation or base result set (`T0`).
80+
1. Run the recursive members with `Ti` as an input and `Ti` + 1 as an output.
81+
1. Repeat step 3 until an empty set is returned.
82+
1. Return the result set. This is a `UNION ALL` of `T0` to `Tn`.
83+
84+
## Examples
85+
86+
The following example shows the semantics of the recursive CTE structure by returning a hierarchical list of employees, starting with the highest ranking employee, in the [!INCLUDE [sssampledbobject-md](../../includes/sssampledbobject-md.md)] database. A walkthrough of the code execution follows the example.
87+
88+
Create an employee table:
89+
90+
```sql
91+
CREATE TABLE dbo.MyEmployees
92+
(
93+
EmployeeID SMALLINT NOT NULL,
94+
FirstName NVARCHAR (30) NOT NULL,
95+
LastName NVARCHAR (40) NOT NULL,
96+
Title NVARCHAR (50) NOT NULL,
97+
DeptID SMALLINT NOT NULL,
98+
ManagerID INT NULL,
99+
CONSTRAINT PK_EmployeeID PRIMARY KEY CLUSTERED (EmployeeID ASC)
100+
);
101+
```
102+
103+
Populate the table with values:
104+
105+
```sql
106+
INSERT INTO dbo.MyEmployees
107+
VALUES
108+
(1, N'Ken', N'Sánchez', N'Chief Executive Officer', 16, NULL),
109+
(273, N'Brian', N'Welcker', N'Vice President of Sales', 3, 1),
110+
(274, N'Stephen', N'Jiang', N'North American Sales Manager', 3, 273),
111+
(275, N'Michael', N'Blythe', N'Sales Representative', 3, 274),
112+
(276, N'Linda', N'Mitchell', N'Sales Representative', 3, 274),
113+
(285, N'Syed', N'Abbas', N'Pacific Sales Manager', 3, 273),
114+
(286, N'Lynn', N'Tsoflias', N'Sales Representative', 3, 285),
115+
(16, N'David', N'Bradley', N'Marketing Manager', 4, 273),
116+
(23, N'Mary', N'Gibson', N'Marketing Specialist', 4, 16);
117+
```
118+
119+
```sql
120+
USE AdventureWorks2008R2;
121+
GO
122+
123+
WITH DirectReports (ManagerID, EmployeeID, Title, DeptID, Level)
124+
AS (
125+
-- Anchor member definition
126+
SELECT e.ManagerID,
127+
e.EmployeeID,
128+
e.Title,
129+
edh.DepartmentID,
130+
0 AS Level
131+
FROM dbo.MyEmployees AS e
132+
INNER JOIN HumanResources.EmployeeDepartmentHistory AS edh
133+
ON e.EmployeeID = edh.BusinessEntityID
134+
AND edh.EndDate IS NULL
135+
WHERE ManagerID IS NULL
136+
UNION ALL
137+
-- Recursive member definition
138+
SELECT e.ManagerID,
139+
e.EmployeeID,
140+
e.Title,
141+
edh.DepartmentID,
142+
Level + 1
143+
FROM dbo.MyEmployees AS e
144+
INNER JOIN HumanResources.EmployeeDepartmentHistory AS edh
145+
ON e.EmployeeID = edh.BusinessEntityID
146+
AND edh.EndDate IS NULL
147+
INNER JOIN DirectReports AS d
148+
ON e.ManagerID = d.EmployeeID)
149+
-- Statement that executes the CTE
150+
SELECT ManagerID,
151+
EmployeeID,
152+
Title,
153+
DeptID,
154+
Level
155+
FROM DirectReports
156+
INNER JOIN HumanResources.Department AS dp
157+
ON DirectReports.DeptID = dp.DepartmentID
158+
WHERE dp.GroupName = N'Sales and Marketing'
159+
OR Level = 0;
160+
GO
161+
```
162+
163+
### Example code walkthrough
164+
165+
The recursive CTE, `DirectReports`, defines one anchor member and one recursive member.
166+
167+
The anchor member returns the base result set `T0`. This is the highest ranking employee in the company. That is, an employee who doesn't report to a manager.
168+
169+
Here's the result set returned by the anchor member:
170+
171+
```output
172+
ManagerID EmployeeID Title Level
173+
--------- ---------- ----------------------------- ------
174+
NULL 1 Chief Executive Officer 0
175+
```
176+
177+
The recursive member returns the direct subordinates of the employee in the anchor member result set. This is achieved by a join operation between the Employee table and the `DirectReports` CTE. It's this reference to the CTE itself that establishes the recursive invocation. Based on the employee in the CTE `DirectReports` as input (`Ti`), the join (`MyEmployees.ManagerID = DirectReports.EmployeeID`) returns as output (`Ti` + 1), the employees who have (`Ti`) as their manager.
178+
179+
Therefore, the first iteration of the recursive member returns this result set:
180+
181+
```output
182+
ManagerID EmployeeID Title Level
183+
--------- ---------- ----------------------------- ------
184+
1 273 Vice President of Sales 1
185+
```
186+
187+
The recursive member is activated repeatedly. The second iteration of the recursive member uses the single-row result set in step 3 (containing an `EmployeeID` of `273`) as the input value, and returns this result set:
188+
189+
```output
190+
ManagerID EmployeeID Title Level
191+
--------- ---------- ----------------------------- ------
192+
273 16 Marketing Manager 2
193+
273 274 North American Sales Manager 2
194+
273 285 Pacific Sales Manager 2
195+
```
196+
197+
The third iteration of the recursive member uses the previous result set as the input value, and returns this result set:
198+
199+
```output
200+
ManagerID EmployeeID Title Level
201+
--------- ---------- ----------------------------- ------
202+
16 23 Marketing Specialist 3
203+
274 275 Sales Representative 3
204+
274 276 Sales Representative 3
205+
285 286 Sales Representative 3
206+
```
207+
208+
The final result set returned by the running query is the union of all result sets generated by the anchor and recursive members.
209+
210+
[!INCLUDE [ssresult-md](../../includes/ssresult-md.md)]
211+
212+
```output
213+
ManagerID EmployeeID Title Level
214+
--------- ---------- ----------------------------- ------
215+
NULL 1 Chief Executive Officer 0
216+
1 273 Vice President of Sales 1
217+
273 16 Marketing Manager 2
218+
273 274 North American Sales Manager 2
219+
273 285 Pacific Sales Manager 2
220+
16 23 Marketing Specialist 3
221+
274 275 Sales Representative 3
222+
274 276 Sales Representative 3
223+
285 286 Sales Representative 3
224+
```
225+
226+
## Related content
227+
228+
- [WITH common_table_expression (Transact-SQL)](with-common-table-expression-transact-sql.md)
229+
- [Query hints (Transact-SQL)](hints-transact-sql-query.md)
230+
- [INSERT (Transact-SQL)](../statements/insert-transact-sql.md)
231+
- [UPDATE (Transact-SQL)](update-transact-sql.md)
232+
- [DELETE (Transact-SQL)](../statements/delete-transact-sql.md)
233+
- [EXCEPT and INTERSECT (Transact-SQL)](../language-elements/set-operators-except-and-intersect-transact-sql.md)

docs/t-sql/queries/with-common-table-expression-transact-sql.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ monikerRange: ">=aps-pdw-2016 || =azuresqldb-current || =azure-sqldw-latest || >
3434

3535
Specifies a temporary named result set, known as a common table expression (CTE). This is derived from a simple query and defined within the execution scope of a single `SELECT`, `INSERT`, `UPDATE`, `MERGE`, or `DELETE` statement. This clause can also be used in a `CREATE VIEW` statement as part of its defining `SELECT` statement. A common table expression can include references to itself. This is referred to as a recursive common table expression.
3636

37+
For more information, see [Recursive queries using common table expressions](recursive-common-table-expression-transact-sql.md).
38+
3739
:::image type="icon" source="../../includes/media/topic-link-icon.svg" border="false"::: [Transact-SQL syntax conventions](../../t-sql/language-elements/transact-sql-syntax-conventions-transact-sql.md)
3840

3941
## Syntax
@@ -115,15 +117,15 @@ When executing a CTE, any hints that reference a CTE can conflict with other hin
115117
> [!NOTE]
116118
> The following guidelines apply to defining a recursive common table expression. For guidelines that apply to nonrecursive CTEs, see [Guidelines for nonrecursive common table expressions](#guidelines-for-nonrecursive-common-table-expressions).
117119
118-
The recursive CTE definition must contain at least two CTE query definitions, an anchor member and a recursive member. Multiple anchor members and recursive members can be defined; however, all anchor member query definitions must be put before the first recursive member definition. All CTE query definitions are anchor members unless they reference the CTE itself.
120+
The [recursive CTE](recursive-common-table-expression-transact-sql.md) definition must contain at least two CTE query definitions, an anchor member and a recursive member. Multiple anchor members and recursive members can be defined; however, all anchor member query definitions must be put before the first recursive member definition. All CTE query definitions are anchor members unless they reference the CTE itself.
119121

120122
Anchor members must be combined by one of these set operators: `UNION ALL`, `UNION`, `INTERSECT`, or `EXCEPT`. `UNION ALL` is the only set operator allowed between the last anchor member and first recursive member, and when combining multiple recursive members.
121123

122124
The number of columns in the anchor and recursive members must be the same.
123125

124126
The data type of a column in the recursive member must be the same as the data type of the corresponding column in the anchor member.
125127

126-
The FROM clause of a recursive member must refer only one time to the CTE *expression_name*.
128+
The `FROM` clause of a recursive member must refer only one time to the CTE *expression_name*.
127129

128130
The following items aren't allowed in the *CTE_query_definition* of a recursive member:
129131

@@ -699,6 +701,7 @@ SELECT TableName, TotalAvg FROM CountCustomer;
699701

700702
## Related content
701703

704+
- [Recursive queries using common table expressions (Transact-SQL)](recursive-common-table-expression-transact-sql.md)
702705
- [CREATE VIEW (Transact-SQL)](../statements/create-view-transact-sql.md)
703706
- [DELETE (Transact-SQL)](../statements/delete-transact-sql.md)
704707
- [EXCEPT and INTERSECT (Transact-SQL)](../language-elements/set-operators-except-and-intersect-transact-sql.md)

docs/toc.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16644,6 +16644,8 @@ items:
1664416644
href: t-sql/queries/updatetext-transact-sql.md
1664516645
- name: WITH common_table_expression
1664616646
href: t-sql/queries/with-common-table-expression-transact-sql.md
16647+
- name: Recursive queries using common table expressions
16648+
href: t-sql/queries/recursive-common-table-expression-transact-sql.md
1664716649
- name: Nested common table expressions in Fabric Warehouse
1664816650
href: t-sql/queries/nested-common-table-expression.md
1664916651
- name: WRITETEXT

0 commit comments

Comments
 (0)