For any operation over a temporary result set, what are the options SEL Server has to offer? We do have aTemp table, a table variable, table valued parameters and of course not to forget table valued function. But with the onset of SQL Server 2005 and onwards, a very powerful feather has been added for the programmers' benefit: Common Table Expression (CTE). It simplifies complex queries and most importantly enables you to recurse, did I just say recurse values. Yes, similar to any programming languages C#, C++, etc. Amazing, isn’t it ! Let's dive deep into this powerful feature and understand how it works and what all it has to offer.
CTE is again a temporary result set derived from the underling definition. For syntax of CTE, please refer to MSDN.
A Simple Illustration: CTE as a Derived Table
We have a simple table
Productsin our database.
Select * from PRODUCTS ProductID ProductDesc ManufacturingDate ExpiryDate IsSalable Price --------------------------------------------------------------------------------------- 1 Biscuits 2011-09-01 00:00:00.000 2012-09-01 00:00:00.000 1 20.00 2 Butter 2010-09-01 00:00:00.000 2011-09-01 00:00:00.000 1 30.00 3 Milk 2011-10-01 00:00:00.000 2011-11-01 00:00:00.000 1 46.00
We have created a simple
ProductsCTEfor displaying all the
20.00. Here CTE performs the job of acting as a simple derived table.
;WITH ProductsCTE(ProdName,Price)( ProductDesc,Price PRODUCTS Price>20. ) * ProductsCTE
ProdName Price -------------------------------------------------- --------------------- Butter 30.00 Milk 46.00 (2 row(s) affected)
Important point that needs a mention is the
SELECTfollowed by the CTE definition, any operation
Mergecan be performed immediately after the CTE and the CTE lasts only for a single such operation. When I say that, what do I mean...
It means that the below code is erroneous:
@T ,@I @T = @I = ;WITH ProductsCTE(ProdName,Price) ( ProductDesc,Price PRODUCTS Price>20. ) @T+@I * ProductsCTE
On executing the code, it results in the below error. Which means I need to execute the
ProductsCTE SELECTimmediately after the CTE definition completes.
Msg 422, Level 16, State 4, Line 10 Common table expression defined but not used.
To rectify the same... I would reinstate the order as:
@T ,@I @T = @I = ;WITH ProductsCTE(ProdName,Price) ( ProductDesc,Price PRODUCTS Price>20. ) * ProductsCTE @T+@I
ProdName Price -------------------------------------------------- --------------------- Butter 30.00 Milk 46.00 (2 row(s) affected) ----------- 30 (1 row(s) affected)
Let’s perform an
;WITH ProductsCTE(ProdName,Price)( ProductDesc,Price PRODUCTS Price>20. ) ProductsCTE Price=50 ProdName= * ProductsCTE ( row(s) affected)
Msg 208, Level 16, State 1, Line 7 Invalid object name 'ProductsCTE'.
The price for
Milkgets duly updated to
50but the next set of
selectdoesn’t work. Always remember, you can hit for a result set once and hit immediately after the CTE definition. Like:
;WITH ProductsCTE(ProdName,Price)( ProductDesc,Price PRODUCTS Price>20. ) * ProductsCTE ProdName,MIN(Price) PRICE ProductsCTE ProdName Price Bread . Butter . Milk . ( row(s) affected) Calling Multiple CTEs
We can have multiple CTEs calls from one single query. Let’s have a look at the example. We have 2 tables:
Let’s implement a CTE to get all the respective class teachers for the students.
;WITH StudCTE(RollNo,StudentName,TeacherID)( ID,Name,TID Student ) ,TeacherCTE(TID,TeacherName) ( ID,Name Teacher ) RollNo,StudentName,TeacherName StudCTE SC TeacherCTE TC SC.TeacherID=TC.TID
We have called 2 CTEs from a single
SELECTand based upon the inner join returned the student-teacher information. That was a simple example to show how multiple CTEs are done.
So what is the big deal about CTE, the deal is when you need some complex queries or operations, trust me nothing goes as good as CTE. Let’s have a look at one of the most commonly encountered complex issues... Duplicates. We have a sample table (
EMP) for the example.
* EMP EID ENAME DEPT Sara IT Rick HR Ted IT Sheldon Accounts Sara IT ( row(s) affected)
For removing the duplicate employee, i.e. ‘
Sara’ from the table, we create a CTE:
;WITH EliminateDup(Eid,Name,Dept,RowID)( Eid,Ename,Dept, ROW_NUMBER() ( Ename,Dept EID) RowID EMP ) EliminateDup RID>1 The query below creates a temporary result : Eid,Ename,Dept, ROW_NUMBER() ( Ename,Dept EID) RowID EMP Eid Ename Dept RowID Rick HR Sara IT Sara IT Sheldon Accounts Ted IT ( row(s) affected)
And later, we remove the duplicate with the
DELETE. Quite simple, isn’t it.
The next and the most important feature is recursion.
UNION ALL, we can make the CTE recursive to formulate a final result. There is an anchor member and a recursive member which may or may not have a terminating condition. Let’s see with an example..
Suppose we have a comma separated
stringand we wish to extract each word from the
Let’s consider the
stringto be ‘
@T ( )= @T =@T+ ;WITH MyCTE(Start,[ ]) ( Start,CHARINDEX( ,@T, ) [ ] [ ]+1 Start,CHARINDEX( ,@T,[ ]+1) [ ] MyCTE [ ]<LEN(@T) ) SUBSTRING(@T,Start,[ ]-Start) MyCTE;
Let’s understand what we have done here... We have an anchor in the form of
For the first pass, the anchor returns the values 1,6 (this value being the
CHARINDEXof first comma after the word ‘
Where,’) for columns Start & [End].
Next the recursive code returns [End]+1=7 as Start and 12 for
CHARINDEX(',',@T,[End]+1, i.e. 7) AS [End], this code recurses unless the terminating condition is met which is [End]<LEN(@T)i.e. 37.
UNION ALLoperator unites all the start & [End], for clarity let's take another look at the values.
@T ( )= @T =@T+ ;WITH MyCTE(Start,[ ]) ( Start,CHARINDEX( ,@T, ) [ ] [ ]+1 Start,CHARINDEX( ,@T,[ ]+1) [ ] MyCTE [ ]<LEN(@T) ) Start,[ ],SUBSTRING(@T,Start,[ ]-Start) String MyCTE;
Hope that makes things clearer. With CTE, we can achieve the same feats of programmability as C# or C++ with respect to generating Fibonacci series, a specific
stringpatterns, etc. The recursion specifically finds an important use while you need a hierarchy to be reported, we will see that in a while. Currently, let’s look into the recursion option.
What if we want the first two values only out of the
@T ( )= @T =@T+ ;WITH MyCTE(Start,[ ]) ( Start,CHARINDEX( ,@T, ) [ ] [ ]+1 Start,CHARINDEX( ,@T,[ ]+1) [ ] MyCTE [ ]<LEN(@T) ) Start,[ ],SUBSTRING(@T,Start,[ ]-Start) String MyCTE (MAXRECURSION );
OPTION MAXRECURSIONenables the code to recurse only once and terminates as soon as that happens.The self explanatory message flashes and the values returned out on the results pane is:
MAXRECURSIONvalue can be between 0 and 32,767. 32,767 is fine but would what a 0 return? 0 enables an infinite recursion hence if the recursive statement does not have a terminating condition, the program loops infinitely. For first hand experience, try the below code?
;WITH MyCTE(Val)( Val Val=(Val+1) MyCTE ) Val MyCTE (MAXRECURSION );
Before we call it a day, let’s look at the final example of fetching the complete hierarchy of a particular organization. In such scenarios, CTE could outperform any complex code both in terms of simplicity and LOC (lines of code) required to derive the result.
We have a table
For fetching the bottom up hierarchy, we pass the eid and get the complete hierarchy for the concerned employee. For example, for Andy’s organizational hierarchy, we pass @T = 6 (His Eid).
@T = ;WITH OrgCTE(Eid,Employee,SupervisorID,ReportsTo) ( @T,O.Name,O2.EID,O2.Name Org O Org O2 O.SupervisorID=O2.EID O.EID=@T OC.SupervisorID,OC.ReportsTo,O2.EID,O2.Name OrgCTE OC Org O OC.SupervisorID=O.EID Org O2 O.SupervisorID=O2.EID ) * OrgCTE
So we have been able to get the hierarchy for Andy.
Similarly for the top down hierarchy, we can implement the below CTE which gives the level indicating the top down org chart.
;WITH OrgCTE(Eid,SupervisorID,Employee,[Role],[Level])( EID,SupervisorID,Name,[Role], Org SupervisorID=0 O.EID,O.SupervisorID,O.Name,O.[Role],[Level]+1 Org O OrgCTE OC O.SupervisorID=OC.Eid ) * OrgCTE
SQL programmers find CTE of immense use and thanks to this feature, complexities in programming life have been considerably simplified. I hope I have been able to justify CTE reasonably well in this article.