More About Common Table Expressions in SQL Server 2005

This is the second part of a tutorial focusing on common table expressions in SQL Server 2005. In the previous article, I explained non-recursive CTEs. Therefore, in this article, I shall mainly focus on recursive CTEs in SQL Server 2005.

Contributed by
Rating: 4 stars4 stars4 stars4 stars4 stars / 17
June 12, 2006
Rate this Article:
MEH MEH++


SEARCH ASP FREE
TOOLS YOU CAN USE

advertisement

If you are new to CTEs, I strongly suggest you go through my first article on the topic here.

All the examples in this article were tested using SQL Server 2005 Enterprise Edition. Please note that I didn’t really test the examples on any of other versions/editions or similar suites of Microsoft products (even though I strongly believe that the examples will work on all SQL Server 2005 editions). 

You also need to consider that the examples are not at all optimized for performance.  They may not be used for a real production environment.  I just wanted to introduce the concept of recursive CTEs through this article without any discussion of performance tuning (which is beyond the scope of this article). 

Coming to the topic, what exactly is a “recursive CTE”?  A CTE which includes references to itself within its own body is called a recursive CTE. Recursion is generally specified using any SET operator (like UNION ALL, UNION, INTERSECT, or EXCEPT) within the CTE. The second part of the query (which is after the SET operator) must refer to the CTE itself, using it as a key to the next level of recursion.

Some clauses like DISTINCT, GROUP BY, HAVING, TOP, LEFT, RIGHT, OUTER JOIN, (INNER JOIN is allowed) along with scalar aggregations, sub queries and so forth are not allowed within a CTE recursive member definition. 

A recursive common table expression: an example

Before talking too much, let us start with a simple example.

SELECT
  EmployeeID
 FROM HumanResources.Employee
 WHERE ManagerID = 140

The above example (even though it is not a CTE) simply displays all EmployeeIDs, who are under manager 140 (the manager id or employee id of the manager).  But the employees under manager 140 may have their own subordinates.  So within the above list we are covering only the first level of subordinates under  manager 140, and not really listing all the levels of subordinates under him or her.

Now let us work with our new CTE example:

WITH EmpCTE
AS
(
 SELECT
  EmployeeID, ManagerID
 FROM HumanResources.Employee
 WHERE ManagerID = 140
 
 UNION ALL
 
 SELECT
  b.EmployeeID, b.ManagerID
 FROM HumanResources.Employee b
 JOIN EmpCTE AS a ON a.EmployeeID = b.ManagerID
)
SELECT EmployeeID,ManagerID
FROM EmpCTE

The sample output for the above CTE would be something like the following:

EmployeeID  ManagerID

----------  ---------

30          140

71          140

103         140

139         140

59          139

94          139

130         139

.

.

.

82          30

154         30

191         30

I shall explain the above script in the next section.

A recursive common table expression: explanation

This section explains the script provided in the previous section.  Let me explain it part by part.  Let us first consider the following code fragment:

SELECT
  EmployeeID, ManagerID
 FROM HumanResources.Employee
 WHERE ManagerID = 140

It is not surprising that it retrieves the list of all employees working under manager 140.  Let us add a very funny structure to the above SELECT statement which ends up as follows:

WITH EmpCTE
AS
(
 SELECT
  EmployeeID, ManagerID
 FROM HumanResources.Employee
 WHERE ManagerID = 140

Even though we don’t come across any remarkable modifications, it has its own depth. Every row retrieved by the SELECT statement will be treated as part of the temporary table named “EmpCTE.”  At this point, it is not of much interest.  Let us proceed with the following statement now:

SELECT
  b.EmployeeID, b.ManagerID
 FROM HumanResources.Employee b
 JOIN EmpCTE AS a ON a.EmployeeID = b.ManagerID

This is the most confusing statement you would ever see (or understand).  The above statement works with every row present in “EmpCTE.”  It primarily retrieves all the subordinates of each employee existing in “EmpCTE” and adds them again to the same “EmpCTE” with the help of the UNION ALL operator. 

This process gets executed recursively for every new row added (or being added) to the same “EmpCTE.”  Finally we retrieve all the information from “EmpCTE” using the following statement:

SELECT EmployeeID,ManagerID
FROM EmpCTE

Displaying the depth of recursion when using recursive CTE: listing levels

Let us start with another example, which gives the level of every employee.

SELECT
  EmployeeID, ManagerID,0 AS EmployeeLevel
 FROM HumanResources.Employee
 WHERE ManagerID is Null

The above example simply displays EmployeeID, ManagerID and EmployeeLevel as "0," for those who are not working under any manager (in this case, it gives us only one employee, who is the CEO).

Now let us work with a recursive CTE:

WITH EmpCTE
AS
(
 SELECT
  EmployeeID, ManagerID,0 AS EmployeeLevel
 FROM HumanResources.Employee
 WHERE ManagerID is Null
 
 UNION ALL
 
 SELECT
  b.EmployeeID, b.ManagerID,EmployeeLevel+1
 FROM HumanResources.Employee b
 INNER JOIN EmpCTE AS a ON a.EmployeeID = b.ManagerID
)
SELECT EmployeeID,ManagerID,EmployeeLevel
FROM EmpCTE
Order By EmployeeLevel

The sample output for the above CTE would be something like the following:

EmployeeID  ManagerID   EmployeeLevel

----------  ---------   -------------

109   NULL  0

6     109   1

12    109   1

.

.

268   273   2

284   273   2

.

.

.

47    30    3

70    30    3

82    30    3

.

.

.

31    210   4

45    210   4

56    210   4

68    210   4

81    210   4

           

I shall explain the above script in the next section.

Displaying the depth of recursion when using recursive CTE: explanation

This section explains the script provided in previous section.  Let me explain it part by part.  Let us first consider the following code fragment:

SELECT
  EmployeeID, ManagerID,0 AS EmployeeLevel
 FROM HumanResources.Employee
 WHERE ManagerID is Null

It is not surprising that it retrieves the list of all employees who are not working under any manager.  Let us add a very funny structure to the SELECT statement, just like the previous example, which ends up as follows:

WITH EmpCTE
AS
(
 SELECT
  EmployeeID, ManagerID,0 AS EmployeeLevel
 FROM HumanResources.Employee
 WHERE ManagerID is Null

Every row retrieved by the SELECT statement will be treated as part of the temporary table named “EmpCTE” (very similar to the first example).  Let us proceed with the following statement now:

SELECT
  b.EmployeeID, b.ManagerID,EmployeeLevel+1
 FROM HumanResources.Employee b
 INNER JOIN EmpCTE AS a ON a.EmployeeID = b.ManagerID

The above SELECT statement is considered to be the recursive part (which really enforces the recursion).  This statement is similar to the previous example; the only difference we find is with the “EmployeeLevel.” The above statement works with every row present in “EmpCTE.”

It gets executed for every row present in “EmpCTE,” and if it happens to give any output of rows, those rows will again be added to “EmpCTE” and again call itself for every newly added row.

This process gets executed recursively for every new row added (or being added) to the same “EmpCTE.”  Finally we retrieve all the information from “EmpCTE” using the following statement:

SELECT EmployeeID,ManagerID,EmployeeLevel
FROM EmpCTE
Order By EmployeeLevel

A recursive common table expression using views: example

Till now we have simple examples. Let us start extending the above example to include both employee names and manager names by creating a recursive CTE using views.

create view vEmpManagerDetails
as
WITH EmployeeManagersDetails(EmployeeID, EmployeeName, ManagerID,
Manager)
 AS (SELECT     a.EmployeeID, a.Name, a.ManagerID,b.Name AS
Manager
       FROM (SELECT     HumanResources.Employee.EmployeeID,
                  Person.Contact.FirstName + ' ' +
Person.Contact.MiddleName
                  + ' ' + Person.Contact.LastName AS Name,
                  HumanResources.Employee.ManagerID
           FROM Person.Contact INNER JOIN
                        HumanResources.Employee ON
               Person.Contact.ContactID =
HumanResources.Employee.ContactID) AS a LEFT OUTER JOIN
          (SELECT     HumanResources.Employee.EmployeeID,
                  Person.Contact.FirstName + ' ' +
Person.Contact.MiddleName
                  + ' ' + Person.Contact.LastName AS Name,
                  HumanResources.Employee.ManagerID
               FROM Person.Contact INNER JOIN
                        HumanResources.Employee ON
               Person.Contact.ContactID =
HumanResources.Employee.ContactID) AS b
                         ON a.ManagerID = b.EmployeeID)
    SELECT     EmployeeID, EmployeeName, ManagerID, Manager
     FROM         EmployeeManagersDetails

The above script creates the view “VEmpManagerDetails” using a non-recursive CTE. This view displays managers and the employees reporting to them.

Now let us create a recursive CTE based on the above view:

WITH EmployeeManagerDetails(ManagerID,Manager,
EmployeeID,EmployeeName, EmployeeLevel) AS
(
    SELECT ManagerID,Manager,EmployeeID, EmployeeName, 0 AS
EmployeeLevel
    FROM dbo.vEmpManagerDetails
    WHERE ManagerID IS NULL
    UNION ALL
    SELECT vem.ManagerID,vem.Manager,
vem.EmployeeID,vem.EmployeeName, EmployeeLevel + 1
    FROM dbo.vEmpManagerDetails vem
        INNER JOIN EmployeeManagerDetails d
        ON vem.ManagerID = d.EmployeeID
)
SELECT ManagerID, Manager,EmployeeID,EmployeeName, EmployeeLevel
+1 as EmployeeLevel
FROM EmployeemanagerDetails order by EmployeeLevel;
GO

I hope everybody can understand the above script. It is similar to the previous example. The only difference we can find is that it displays the Names of Employees and Managers along with their IDs and levels.  I also added “EmployeeLevel + 1” just to eliminate the “0” in the output.

The sample output for the above CTE would be something like the following:

ManagerID   Manager     EmployeeID  EmployeeName            EmployeeLevel

--------    ---------   ----------  ------------            -------------

NULL        NULL        109         Ken J Sánchez           1

109         Ken J..     6           David M Bradley         2

109         Ken J..     12          Terri Lee Duffy         2

.

.

.

273         Brian..     268         Stephen Y Jiang         3

273         Brian..     284         Amy E Alberts           3

273         Brian..     288         Syed E Abbas            3

.

.

.

30          Paula M..   47          Willis T Johnson        4

30          Paula M..   70          Mindy C Martin          4

30          Paula M..   82          Vidur X Luthra          4

30          Paula M..   154         Hao O Chen              4

.

.

.

210         Brenda..    31          Alejandro E McGuel      5

210         Brenda..    45          Fred T Northup          5

210         Brenda..    56          Kevin H Liu             5

210         Brenda..    68          Shammi G Mohamed        5

.

.

.

Any comments, suggestions, ideas, improvements, bugs, errors, feedback etc. are highly appreciated at jag_chat@yahoo.com.

blog comments powered by Disqus
MS SQL SERVER ARTICLES

- Idera Releases SQL Diagnostic Manager v7.1
- MS SQL Sever 2012 Launch, New Idera Release
- OpenText Azure Cloud Solution, Geminaire Raa...
- Melissa Data Releases MatchUp Tool for SQL S...
- Glovia`s G2 ERP Solution to Support SQL Serv...
- Upgrade Assistant for SQL Server 2012 Releas...
- Azure Update Features Several New Improvemen...
- NT OBJECTives SQL Invader Tool Offers Free V...
- SQL Server ODBC Driver for Red Hat Enterpris...
- Heroku Postgres: A New SQL Database-as-a-Ser...
- Idera Compliance Manager 3.5 and SQL Server ...
- Microsoft and Joyent Announce Node.js Window...
- How to Install Xampp on Windows XP
- SQL Server 2008 SP3 and HP Database Enterpri...
- How To Install Windows Azure

ASP Web Hosting ASP.Net Web Hosting Windows Web Hosting
 
 
 

ASP Free Forums 
 RSS  Tutorials RSS
 RSS  Forums RSS
 RSS  All Feeds
Site Map 
Request Media Kit
Write For Us Get Paid 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
Privacy Policy 
Support 


© 2003-2012 by Developer Shed. All rights reserved. DS Cluster 3 - Follow our Sitemap
Most Popular Topics
All ASP.Net Tutorials