Common Table Expressions(CTEs) are new to SQL Server 2005. CTEs are defined in the ANSI SQL-99 standard. We need to begin learning about the new features of SQL 2005, and CTEs are an interesting place to start. You will learn about the syntax and how to think about CTEs. SQL Server 2005 training from Learnkey (www.Learnkey.com) will go into the details of all of the new features of SQL 2005.
CTEs come in 2 forms, nonrecursive and recursive. In this whitepaper we will look only at nonrecursive CTEs. Next time we will do recursive CTEs it is amazing what you can do with a recursive CTE.
You can think of a nonrecursive CTE (NR-CTE) as a derived table, or a temporary view. Microsoft refers to a CTE as a temporary, named result set. Most of the NR-CTEs can be re-written as a longer statement which uses derived tables or subqueries. Since a CTE can be referred to multiple times in a query, the basic benefit you get from NR-CTEs is simplicity of syntax in your queries.
The basic syntax for a CTE follows:
With CTE_Name (Column_Name_1, Column_Name_2 Column_Name_n ) As ( cte_Query_Definition )
--TSQL which uses the CTE
Listing 1. Basic Syntax for CTE
Lets take a look at a simple NR-CTE which lists each Manager and his number of Direct Reports.
USE Advent ur eWor ks; GO WI TH Di r Reps( Manager I D, Di r ect Repor t s) AS ( SELECT Manager I D, COUNT( *) FROM HumanResour ces. Empl oyee AS e WHERE Manager I D I S NOT NULL GROUP BY Manager I D ) SELECT Manager I D, Di r ect Repor t s FROM Di r Reps ORDER BY Manager I D; GO
The St at ement pr oduces t he r esul t bel ow( Li st i ng 2 CTE Quer y Resul t s ) . ( The l i st i s t r uncat ed f or br evi t y. ) Now l et s l ook at t he CTE. The CTE def i ni t i on begi ns
WI TH Di r Reps( Manager I D, Di r ect Repor t s) AS ( ) Di r Reps i s t he name of t he CTE. The CTE name may be t he same as a t abl e/ vi ew name used i n t he expr essi on, but I suggest you make t he CTE Names di f f er ent . Dupl i cat e names ar e t oo conf usi ng at l east f or me. I f your CTE name i s t he same as a t abl e name, any r ef er ence woul d poi nt t o t he CTE name. You wi l l see l at er t hat you may i ncl ude mul t i pl e CTEs wi t hi n t he same WI TH cl ause. CTE Names MUST be uni que wi t hi n t he same WI TH cl ause.
You may opt i onal l y r ename t he col umns whi ch ar e r et ur ned f r omt he CTE, i n t he same way you can pr ovi de col umn names i n a vi ew def i ni t i on. I f t he col umn names ar e not suppl i ed, t he col umn names used i n t he sel ect st at ement ar e used.
I n par ent hesi s i s t he CTE quer y def i ni t i on. The r ul es whi ch appl y t o a NR- CTE quer y ar e exact l y t he same as t he r ul es whi ch appl y t o t he sel ect st at ement when cr eat i ng a vi ew. Hopef ul l y, now you can see t hat t he NR- CTE def i ni t i on i s ver y much l i ke a der i ved t abl e, and al most exact l y l i ke a Cr eat e Vi ew def i ni t i on.
The CTE quer y Def i ni t i on may not cont ai n: COMPUTE or COMPUTE BY ORDER BY ( except when a TOP cl ause i s speci f i ed) I NTO OPTI ON cl ause wi t h quer y hi nt s FOR XML FOR BROWSE
The T- SQL st at ement whi ch uses t he CTE f ol l ows t he CTE def i ni t i on. I n t hi s case:
SELECT Manager I D, Di r ect Repor t s FROM Di r Reps ORDER BY Manager I D;
As you can see t he CTE name, Di r Reps, i s used exact l y l i ke a t abl e, vi ew or der i ved t abl e al i as. The T- SQL st at ement whi ch uses t he CTE def i ni t i on can be a Sel ect , I nser t , Updat e, or Del et e. The CTE def i ni t i on s scope i s t he scope of t he quer y. When t he st at ement i s compl et e t he def i ni t i on i s gone.
Li st i ng 2 CTE Quer y Resul t s
Now l et s r e- wr i t e t he t he quer y wi t hout usi ng a CTE.
SELECT Manager I D, COUNT( *) as Di r ect Repor t s FROM HumanResour ces. Empl oyee AS e WHERE Manager I D I S NOT NULL GROUP BY Manager I D ORDER BY Manager I D; GO
For the example above, I would prefer to use the regular query, instead of the NR-CTE for its brevity and ease of understanding.
You can also put the CTE into a view if you wish.
Cr eat e Vi ew Di r ect Repor t s as WI TH Di r Reps( Manager I D, Di r ect Repor t s) AS ( SELECT Manager I D, COUNT( *) FROM HumanResour ces. Empl oyee AS e WHERE Manager I D I S NOT NULL GROUP BY Manager I D ) SELECT Manager I D, Di r ect Repor t s FROM Di r Reps GO Sel ect * Fr omDi r ect Repor t s ORDER BY Manager I D;
As you can see, the NR-CTE is really not much different than using a derived table. However if you find yourself in a situation where you would have to create the derived table several times in a query, using a CTE will be more succinct.
Lets take a look at another example. We wish to have a listing of each Employee and the number of Orders they have taken for 2004 and 2003(Listing 3 Employee Order History.).
Listing 3 Employee Order History.
One way we mi ght wr i t e t he quer y t o pr oduce t hi s woul d be t o make a der i ved t abl e f or t he empl oyeei d, or der year and number of Or der s f or 2004.
SELECT Sal esPer sonI D, COUNT( *) as Number Of Or der s, YEAR( Or der Dat e) as Or der Year FROM Sal es. Sal esOr der Header as Tar get Year GROUP BY Sal esPer sonI D, YEAR( Or der Dat e) WHERE Tar get Year . Or der Year = 2004
Then do t he same t o get t he 2003 number s. Not i ce t hey ar e t he same. We wi l l use WHERE cl auses l at er t o l i mi t t he r ows whi ch ar e r et ur ned.
SELECT Sal esPer sonI D, COUNT( *) as Number Of Or der s, YEAR( Or der Dat e) as Or der Year FROM Sal es. Sal esOr der Header as Pr i or Year GROUP BY Sal esPer sonI D, YEAR( Or der Dat e) WHERE Pr i or Year . Or der Year = 2003
Then we wi l l use bot h t he above quer i es as der i ved t abl es i n t he compl et e quer y. We do t hi s by j oi ni ng t he der i ved t abl es. The Empl oyee t abl e i s al so j oi ned t o l ook up t he Empl oyeeI D f r omt he Sal ePer sonI d. To si mpl i f y mai nt enance we al so change t he year sel ect i on f or t he Pr i or Year der i ved t abl e:
SELECT E. Empl oyeeI D, Tar get Year . Or der Year , Tar get Year . Number Of Or der s, Pr i or Year . Or der Year , Pr i or YEar . Number Of Or der s FROM HumanResour ces. Empl oyee AS E I NNER J OI N ( SELECT Sal esPer sonI D, COUNT( *) as Number Of Or der s, YEAR( Or der Dat e) as Or der Year FROM Sal es. Sal esOr der Header GROUP BY Sal esPer sonI D, YEAR( Or der Dat e) ) AS Tar get Year ON E. Empl oyeeI D = Tar get Year . Sal esPer sonI D LEFT OUTER J OI N ( SELECT Sal esPer sonI D, COUNT( *) as Number Of Or der s , YEAR( Or der Dat e) as Or der Year FROM Sal es. Sal esOr der Header GROUP BY Sal esPer sonI D, YEAR( Or der Dat e) ) as Pr i or Year ON E. Empl oyeeI D = Pr i or Year . Sal esPer sonI D AND Pr i or Year . Or der Year = Tar get Year . Or der Year - 1 WHERE Tar get Year . Or der Year = 2004 ORDER BY E. Empl oyeeI D;
Not i ce t hat t he der i ved t abl es ar e t he same, but have t o be wr i t t en t wi ce. Thi s makes t he quer y l onger , and mor e l i kel y t o be buggy. We can shor t en t hi s by usi ng a NR- CTE. Make t he der i ved t abl e t he NR- CTE. Then wr i t e a SQL Quer y whi ch j oi ns t he CTE t o i t sel f usi ng al i ases. Not i ce t he i nt r oduct i on of t he l ocal var i abl e. You may r ef er t o l ocal var i abl es i n a CTE or t he quer y whi ch uses t he CTE.
DECLARE @Year i nt SELECT @Year =2004 ; WI TH Sal es_CTE ( Sal esPer sonI D, Number Of Or der s, Or der Year ) AS ( SELECT Sal esPer sonI D, COUNT( *) , YEAR( Or der Dat e) FROM Sal es. Sal esOr der Header GROUP BY Sal esPer sonI D, YEAR( Or der Dat e) ) SELECT E. Empl oyeeI D, Tar get Year . Or der Year , Tar get Year . Number Of Or der s, Pr i or Year . Or der Year , Pr i or YEar . Number Of Or der s FROM HumanResour ces. Empl oyee AS E I NNER J OI N Sal es_CTE AS Tar get Year ON E. Empl oyeeI D = Tar get Year . Sal esPer sonI D LEFT OUTER J OI N SALES_CTE Pr i or Year ON E. Empl oyeeI D = Pr i or Year . Sal esPer sonI D AND Pr i or Year . Or der Year = Tar get Year . Or der Year - 1 WHERE Tar get Year . Or der Year = @Year ORDER BY E. Empl oyeeI D;
Also take note of the semicolon prior to the definition of the CTE. Take the semicolon out and run this CTE. You will get the error:
Msg 319, Level 15, St at e 1, Li ne 228 I ncor r ect synt ax near t he keywor d ' wi t h' . I f t hi s st at ement i s a common t abl e expr essi on or an xml namespaces cl ause, t he pr evi ous st at ement must be t er mi nat ed wi t h a semi col on.
Whenever you use a CTE in a batch with other statements in front of it, the last statement in front of the CTE must be terminated with a semicolon. Perhaps it is a good practice to always prefix CTEs with a semicolon.
The last item we will see is an example of multiple queries defined in the same CTE expression. This particular example gives us the names of the top 5 and bottom 5 sales people. You can use either Sales_CTE or TopSellers or both in your query. Note that the TopSellers query also refers to Sales_CTE. One CTE query can refer to a query defined physically above it in the expression, but can NOT refer to a query defined after it. The queries do not HAVE to refer to one another, they may be totally independent.
DECLARE @Year i nt SELECT @Year =2004 ; WI TH Sal es_CTE ( Sal esPer sonI D, Cont act I D, Number Of Or der s, Or der Year ) AS ( SELECT Sal esPer sonI D, Cont act I D, COUNT( *) , YEAR( Or der Dat e) FROM Sal es. Sal esOr der Header WHERE YEAR( Or der Dat e) = @Year GROUP BY Sal esPer sonI D, Cont act I D, YEAR( Or der Dat e) ) , TopSel l er s( Ful l Name, Cont act I D, Number Of Or der s, Or der Year , Cl ass) AS ( SELECT TOP 5 Fi r st name + ' ' + Last name, SCTE. Cont act I D, Number Of Or der s, Or der Year , ' TopSel l er ' FROM Per son. Cont act c I NNER J OI N Sal es_CTE AS SCTE ON C. Cont act I D = SCTE. Cont act I D ORDER BY Number of Or der s Desc UNI ON ALL SELECT TOP 5 Fi r st name + ' ' + Last name, SCTE. Cont act I D, Number Of Or der s, Or der Year , ' LowSel l er ' FROM Per son. Cont act c I NNER J OI N Sal es_CTE AS SCTE ON C. Cont act I D = SCTE. Cont act I D ORDER BY Number of Or der s Asc )
SELECT * f r omTopSel l er s
Thats it for NR-CTEs. When they make the query easier to understand use them. You may find that you use them when you need to take the results of a Group By, and join those results to another table.
Next time we will took at Recursive CTEs. Recursive CTEs is where the REAL POWER is. If you have ever tried to navigate a parent child relationship, hierarchy or network you know how painful it can be, especially if you do not know how deep the hierarchy goes, or if it is ragged. Recursive CTEs, will do a depth first traversal of a hierarchy so easily. You are going to be pleased and surprised.
See you next time!
Wayne Snyder is a long time Learnkey author (www.learnkey.com). Learnkeys SQL 2000 Administration Series won the SQL Server Magazines Best Web Based Training in 2004. He is an author for The SQL Server Standard and SQL Server Magazine, an MCT, SQL Server MVP, and Managing Consultant for Mariner(www.mariner-usa.com). Wayne has worked with SQL since its first release, and currently focuses on SQL Business Intelligence.