A CTE is a temporary result set introduced in SQL Server 2005 that simplifies complex queries by breaking them into smaller parts. It works with SELECT, INSERT, UPDATE, and DELETE statements and doesnβt use permanent memory.
A recursive CTE references itself to return subsets of data until all results are retrieved. It is especially useful for handling hierarchical data, like organizational charts, by joining all levels of the hierarchy.
Syntax:
WITH RECURSIVE cte_name [(col1, col2, ...)] AS ( subquery ) Select col1, col2, .. from cte_name;
In the above syntax:
cte_name: Name given to recursive subquery written in subquery block.
col1, col2, ...colN: The name given to columns generated by subquery.
Subquery: A MySql query that refer to itself using cte_name as its own name
The workflow of RECURSIVE CTE is majorly consists of these 5 steps:
Anchor Member Execution: The CTE starts it's execution with the anchor memberwhich is anon recursive query. It initializes the initial rows of the CTE that serves as the starting point of the recursion.
Recursive Member Execution(Iterations): The recursive member mainly consists of the SELECT statement that references the CTE itself. Each iteration uses the results that were obtained in the previous iteration or the initial anchor member.This process is repeated until a termination condition is met.
Termination Condition Check: The termination condition is essential for the termination of the recursive query in the CTE or else our query can get stuck in an infinite loop. This mainly consists of WHERE clause that filters the rows. When the termination condition is met the recursive member halts the execution and returns the result set.
Union Result Sets: The UNION ALL operator combines the results from the anchor member as well as the results obtained from all of the iterations of the recursive member. At this step the final result set is prepared which will be returned to the user.
Return Final Result: Finally the result set formed in the previous steps is returned to the user as the output generated by the recursive CTE.
Examples of Recursive CTE in SQL server:
Let us consider the following table Organizationon which we want to find the Hierarchical relationships between the employees and managersof a respective organization.
Example 1: Find Hierarchical Relationship of All Levels
Query:
WITH RecursiveOrganizationCTE AS ( SELECT EmployeeID, FirstName, LastName, Department,ManagerID FROM Organization WHERE ManagerID IS NULL UNION ALL
SELECT e.EmployeeID, e.FirstName, e.LastName, e.department,e.ManagerID FROM Organization e JOIN RecursiveOrganizationCTE r ON e.ManagerID = r.EmployeeID ) --Show the records stored inside the CTE we created above SELECT * FROM RecursiveOrganizationCTE;
Query uses Recursive CTE with DATENAME() to list all months of the year.
DATENAME() returns the month name from its number.
Anchor member starts at 1, giving January.
Recursive member increments monthnumber by 1 to generate months from February to December.
Example 4: Find Relationship Between Data Present in the Different Tables Through Recursive CTE.
In the following example we will see how we can use the Recursive CTE to extract meaningful data from multiple tables. Let us consider two tables CityData and CityRoutes on which we will perform our operations.
After inserting some data into the CityDataTable, The Table Looks:
WITH Destinations AS ( SELECT CR.RouteID, CR.SourceCityID, CR.DestinationCityID, CAST(CS.CityName + ' -> ' + CD.CityName AS VARCHAR(MAX)) AS Route, CR.Distance FROM CityRoutes CR INNER JOIN CityData CS ON CR.SourceCityID = CS.CityID INNER JOIN CityData CD ON CR.DestinationCityID = CD.CityID
UNION ALL
SELECT CR.RouteID, R.SourceCityID, CR.DestinationCityID, CAST(R.Route + ' -> ' + CD.CityName AS VARCHAR(MAX)), R.Distance + CR.Distance FROM Destinations R INNER JOIN CityRoutes CR ON R.DestinationCityID = CR.SourceCityID INNER JOIN CityData CD ON CR.DestinationCityID = CD.CityID WHERE CHARINDEX('-> ' + CD.CityName, R.Route) = 0 ) SELECT Route, Distance FROM Destinations WHERE Route LIKE '%Bangalore%' OPTION (MAXRECURSION 0);