Sunteți pe pagina 1din 8

CTE In SQL Server

Keshav Singh, 1 Nov 2011

CPOL

4.85 32 votes
CTE In SQL Server

Introduction
For any operation over a temporary result set, what are the options SEL Server has to offer? We do have a Temp 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 torecurse, did I just say recurse values. Yes, similar to any
programming languages C#, C++, etc. Amazing, isnt 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*fromPRODUCTS
ProductIDProductDescManufacturingDateExpiryDateIsSalablePrice

1 Biscuits2011090100:00:00.0002012090100:00:00.0001
20.00
2 Butter2010090100:00:00.0002011090100:00:00.0001
30.00
3 Milk 2011100100:00:00.0002011110100:00:00.000146.00
We have created a simple ProductsCTEfor displaying all the Productswith Pricegreater than 20.00. Here
CTE performs the job of acting as a simple derived table.
;WITHProductsCTE(ProdName,Price)AS
(SELECTProductDesc,Price
FROMPRODUCTS
WHEREPrice>20.00
)
SELECT*FROMProductsCTE
ProdNamePrice

Butter30.00
Milk46.00
(2row(s)affected)
Important point that needs a mention is the SELECTfollowed by the CTE definition, any operation SELECT, INSERT,
UPDATE, DELETEor 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:
DECLARE@TINT,@IINT
SET@T=10
SET@I=20
;WITHProductsCTE(ProdName,Price)AS
(SELECTProductDesc,Price
FROMPRODUCTS
WHEREPrice>20.00
)
SELECT@T+@I
SELECT*FROMProductsCTE
On executing the code, it results in the below error. Which means I need to execute the ProductsCTESELECT
immediately after the CTE definition completes.
Msg422,Level16,State4,Line10
Commontableexpressiondefinedbutnotused.
To rectify the same... I would reinstate the order as:
DECLARE@TINT,@IINT
SET@T=10
SET@I=20
;WITHProductsCTE(ProdName,Price)AS
(SELECTProductDesc,Price
FROMPRODUCTS
WHEREPrice>20.00
)
SELECT*FROMProductsCTE
SELECT@T+@I
ProdNamePrice

Butter30.00
Milk46.00
(2row(s)affected)

30
(1row(s)affected)
Lets perform an UPDATE:
;WITHProductsCTE(ProdName,Price)AS
(SELECTProductDesc,Price
FROMPRODUCTS
WHEREPrice>20.00
)
UPDATEProductsCTESETPrice=50WHEREProdName='Milk'
SELECT*FROMProductsCTE

(1row(s)affected)
Msg208,Level16,State1,Line7
Invalidobjectname'ProductsCTE'.
The price for Milkgets duly updated to 50but the next set of selectdoesnt work. Always remember, you can hit
for a result set once and hit immediately after the CTE definition. Like:

;WITHProductsCTE(ProdName,Price)AS
(SELECTProductDesc,Price
FROMPRODUCTS
WHEREPrice>20.00
)
SELECT*FROMProductsCTE
UNION
SELECT'Bread'ASProdName,MIN(Price)ASPRICEfromProductsCTE
ProdNamePrice

Bread30.00
Butter30.00
Milk50.00
(3row(s)affected)
CallingMultipleCTEs
We can have multiple CTEs calls from one single query. Lets have a look at the example. We have 2 tables:
Select*fromStudent

Select*fromTeacher

Lets implement a CTE to get all the respective class teachers for the students.
;WITHStudCTE(RollNo,StudentName,TeacherID)
AS
(
SELECTID,Name,TIDFROMStudent
)
,TeacherCTE(TID,TeacherName)
AS
(
SELECTID,NameFROMTeacher
)
SELECTRollNo,StudentName,TeacherName
FROMStudCTESC
INNERJOIN
TeacherCTETC
ONSC.TeacherID=TC.TID

We have called 2 CTEs from a single SELECTand based upon the inner join returned the studentteacher information.
That was a simple example to show how multiple CTEs are done.

Complex Scenarios
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. Lets have a look at one of the most commonly encountered complex issues... Duplicates. We have a
sample table EMP for the example.
Select*fromEMP
EIDENAMEDEPT

1SaraIT
2RickHR
3TedIT
4SheldonAccounts
5SaraIT
(5row(s)affected)
For removing the duplicate employee, i.e. Sara from the table, we create a CTE:
;WITHEliminateDup(Eid,Name,Dept,RowID)AS
(
SELECTEid,Ename,Dept,ROW_NUMBER()OVER(PARTITIONBYEname,DeptORDERBYEID)ASRowID
FROMEMP
)
DELETEFROMEliminateDupWHERERID>1
Thequerybelowcreatesatemporaryresultsetas:
SELECTEid,Ename,Dept,ROW_NUMBER()OVER(PARTITIONBYEname,DeptORDERBYEID)ASRowID
FROMEMP
EidEnameDeptRowID

2RickHR1
1SaraIT1
5SaraIT2
4SheldonAccounts1
3TedIT1
(5row(s)affected)
And later, we remove the duplicate with the DELETE. Quite simple, isnt it.

Recursion
The next and the most important feature is recursion.
With the UNIONALL, 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. Lets see with an example..

Suppose we have a comma separated stringand we wish to extract each word from the string..
Lets consider the stringto be Where,there,is,a,will,there,is,a,way.
DECLARE@TVARCHAR(100)='Where,there,is,a,will,there,is,a,way'
SET@T=@T+','
;WITHMyCTE(Start,[End])AS(
SELECT1ASStart,CHARINDEX(',',@T,1)AS[End]
UNIONALL
SELECT[End]+1ASStart,CHARINDEX(',',@T,[End]+1)AS[End]fromMyCTEwhere[End]<LEN(@T)
)
SelectSUBSTRING(@T,Start,[End]Start)fromMyCTE;

Lets understand what we have done here... We have an anchor in the form of SELECT1,CHARINDEX(',',@T,1).
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@Ti.e. 37.
The UNIONALLoperator unites all the start & [End], for clarity let's take another look at the values.
DECLARE@TVARCHAR(100)='Where,there,is,a,will,there,is,a,way'
SET@T=@T+','
;WITHMyCTE(Start,[End])AS(
SELECT1ASStart,CHARINDEX(',',@T,1)AS[End]
UNIONALL
SELECT[End]+1ASStart,CHARINDEX(',',@T,[End]+1)AS[End]fromMyCTEwhere[End]<LEN(@T)
)
SelectStart,[End],SUBSTRING(@T,Start,[End]Start)ASStringfromMyCTE;

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, lets look into the recursion option.
What if we want the firsttwo values only out of the string?
DECLARE@TVARCHAR(100)='Where,there,is,a,will,there,is,a,way'
SET@T=@T+','
;WITHMyCTE(Start,[End])AS(
SELECT1ASStart,CHARINDEX(',',@T,1)AS[End]
UNIONALL
SELECT[End]+1ASStart,CHARINDEX(',',@T,[End]+1)AS[End]fromMyCTEwhere[End]<LEN(@T)
)
SelectStart,[End],SUBSTRING(@T,Start,[End]Start)ASStringfromMyCTE
OPTION(MAXRECURSION1);
The OPTIONMAXRECURSIONenables 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:
1. initial anchor value
2. first recursed value

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?
;WITHMyCTE(Val)AS(
SELECT1ASVal
UNIONALL
SELECTVal=(Val+1)FROMMyCTE
)
SelectValfromMyCTE
OPTION(MAXRECURSION0);

Fetching Hierarchy
Before we call it a day, lets 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 Orgas below:
Select*fromOrg

For fetching the bottom up hierarchy, we pass the eid and get the complete hierarchy for the concerned employee. For
example, for Andys organizational hierarchy, we pass @T = 6 His Eid.
DECLARE@TINT=6
;WITHOrgCTE(Eid,Employee,SupervisorID,ReportsTo)AS
(
SELECT@T,O.Name,O2.EID,O2.NameFROMOrgO
INNERJOINOrgO2ONO.SupervisorID=O2.EID
ANDO.EID=@T
UNIONALL
SELECTOC.SupervisorID,OC.ReportsTo,O2.EID,O2.Name
FROMOrgCTEOC
INNERJOIN
OrgO
ONOC.SupervisorID=O.EID
INNERJOIN
OrgO2
ONO.SupervisorID=O2.EID
)
SELECT*FROMOrgCTE

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.
;WITHOrgCTE(Eid,SupervisorID,Employee,[Role],[Level])AS
(
SELECTEID,SupervisorID,Name,[Role],0FROM
OrgWHERESupervisorID=0

UNIONALL
SELECTO.EID,O.SupervisorID,O.Name,O.[Role],[Level]+1
FROMOrgO
INNERJOINOrgCTEOC
ONO.SupervisorID=OC.Eid
)
SELECT*FROMOrgCTE

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.

History
31st October, 2011: Initial version

License
This article, along with any associated source code and files, is licensed under The Code Project Open License CPOL

Share
About the Author
Keshav Singh
Database Developer
India

I am a Microsoft certified Technology Specialist in MS SQL Server 2008 and 2005.I have fair amount of experience and
expertise in MS database development, administration and modeling and MS BI. Started my career in 2007 and primarily
into MS databases, and later diversified into MS BI especially SSIS. I also have a little exposure in Oracle 10g while
working on one of the migration projects. But MS SQL Server is my passion!
Firm believer in knowledge grows from sharing, I really like reading books, trying new features and sharing the little that

S-ar putea să vă placă și