You are on page 1of 7

Common Table Expressions

Wayne Snyder, Mariner (www.mariner-usa.com)



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.

Watch for LearnKeys SQL 2005 release!

You might also like