Login or Sign Up to become a member!
LessThanDot Sit Logo

LessThanDot

Data Management

Less Than Dot is a community of passionate IT professionals and enthusiasts dedicated to sharing technical knowledge, experience, and assistance. Inside you will find reference materials, interesting technical discussions, and expert tips and commentary. Once you register for an account you will have immediate access to the forums and all past articles and commentaries.

LTD Social Sitings

Lessthandot twitter Lessthandot Linkedin Lessthandot friendfeed Lessthandot facebook Lessthandot rss

Note: Watch for social icons on posts by your favorite authors to follow their postings on these and other social sites.

Your profile

    Search

    XML Feeds

    Google Ads

    « 6 ways to import data into SQL ServerUsing Mirroring to Reduce DB Migration Downtime (Part 2) »
    comments

    One of my favorite additions to T-SQL language in SQL Server 2005 is Common Table Expressions, CTE for short. Primarily I use them instead of derived tables to improve maintenance and code readability.

    T-SQL Tuesday logo

    See also this interesting article on recursive CTE implementation T-SQL: Using common table expressions (CTE) to generate sequences and this interesting discussion about commonly-known problem of splitting delimited values.

    You may find this article by SQLUSA (Kalman Toth) very interesting.

    I think you will find these blogs by Brad Schulz Viva la Famiglia! and This Article On Recurson Is Entitled "This Article On Recursion Is Entitled "This Article... interesting and useful and enjoyable reading.

    See also Challenge 17 and its solutions at Challenge 17 - Winners

    For instance, this code is much easier to read now after moving some logic into CTE

    1. ;WITH
    2.  
    3. -- -----------------------------------------------
    4.  
    5. -- Calcualtes sum of active taxes
    6.  
    7. -- for each of Tax Groups
    8.  
    9. -- -----------------------------------------------
    10.  
    11. Taxes as
    12.  
    13. (
    14.  
    15.   SELECT tg.nTaxGroupID             as nTaxGroupID,
    16.  
    17.          tg.cName                   as TaxGroupName,
    18.  
    19.          Sum(isNull(ti.nPercent,0)) as TaxPercent
    20.  
    21.     FROM TaxGroups                tg
    22.  
    23.     LEFT JOIN TaxGroupsToTaxItems tgi on tg.nTaxGroupID = tgi.nTaxGroupID
    24.  
    25.     LEFT JOIN TaxItems            ti  on tgi.nTaxItemID = ti.nTaxItemID
    26.  
    27.    WHERE tg.lActive = 1
    28.  
    29.    GROUP BY tg.nTaxGroupID,
    30.  
    31.             tg.cName
    32.  
    33. ),
    34.  
    35. -- -----------------------------------------------
    36.  
    37. -- calculates total cost per Job
    38.  
    39. -- -----------------------------------------------
    40.  
    41. JDS as
    42.  
    43. (
    44.  
    45.   SELECT nJobHeaderID,
    46.  
    47.          Sum(nAmount * nQuantity) as TotalAmount
    48.  
    49.     FROM JobDetail
    50.  
    51.    GROUP BY nJobHeaderID
    52.  
    53. ),
    54.  
    55. -- -----------------------------------------------
    56.  
    57. -- concatenates all Customer's phone numbers
    58.  
    59. -- into one string (LF delimited)
    60.  
    61. -- -----------------------------------------------
    62.  
    63. P as
    64.  
    65. (
    66.  
    67.   SELECT nCustomerID,
    68.  
    69.          Stuff(PhoneInfo, 1, 1, '') AS PhoneInfo      
    70.  
    71.     FROM ( SELECT InnerData.nCustomerID,
    72.  
    73.                   ( SELECT Char(10) + P.cPhoneNumber + ' - ' + rTrim(PT.cType)
    74.                            
    75.                       FROM PhoneNumbers   P
    76.  
    77.                      INNER JOIN PhoneType PT on P.nPhoneTypeID = PT.nPhoneTypeID
    78.  
    79.                      WHERE P.nCustomerID = InnerData.nCustomerID
    80.  
    81.                        FOR XML PATH('') -- this does string concatenation
    82.  
    83.                   ) as PhoneInfo
    84.  
    85.              FROM ( SELECT DISTINCT nCustomerID
    86.  
    87.                       FROM PhoneNumbers
    88.  
    89.                   ) as InnerData
    90.  
    91.          ) as OuterData
    92.  
    93. )          
    94.  
    95. SELECT J.nRouteHeaderID   as nRouteHeaderID,
    96.  
    97.        JH.nCustomerID     as nCustomerID,
    98.  
    99.        JH.nJobHeaderID    as JobHeaderID,
    100.  
    101.        J.ScheduleTime     as ScheduleTime,
    102.  
    103.        JH.cContact        as Contact,
    104.  
    105.        J.SplitAmount      as SplitAmount,
    106.  
    107.        vJ.cAddr1          as Address1,
    108.  
    109.        vJ.cAddr2          as Address2,
    110.  
    111.        vJ.cCity           as City,
    112.  
    113.        vJ.cState          as State,
    114.  
    115.        vJ.cZip            as Zip,
    116.  
    117.        vJ.JobDescription  as JobDescription,
    118.  
    119.        JH.nDiscountPercent/100 * JDS.TotalAmount
    120.  
    121.                           as DiscountAmount,
    122.  
    123.        JH.nAmount         as JobTotal,
    124.  
    125.        T.TaxGroupName     as TaxType,
    126.  
    127.        T.TaxPercent       as TaxPercent,
    128.  
    129.        J.PermanentNote    as PermanentNote,
    130.  
    131.        P.PhoneInfo        as PhoneInfo
    132.  
    133.   FROM @Jobs               J
    134.  
    135.  INNER JOIN dbo.JobHeader JH  on J.nJobHeaderID   = JH.nJobHeaderID
    136.  
    137.   LEFT JOIN v_JobAddress  vJ  on J.nJobHeaderID   = vJ.nJobHeaderID
    138.  
    139.   LEFT JOIN Taxes         T   on JH.nTaxGroupID   = T.nTaxGroupID
    140.  
    141.   LEFT JOIN JDS           JDS on JH.nJobHeaderID  = JDS.nJobHeaderID
    142.  
    143.   LEFT JOIN P             P   on JH.[nCustomerID] = P.[nCustomerID]
    144.  
    145.  ORDER BY J.IdField -- Used for order but not retrieved

    One of the most interesting applications of CTE is in solving hierarchical queries by using recursive CTEs. In SQL Server 2000 you would need to use temporary table and looping to solve the problem of hierarchical query, which can now be solved with 1 select in SQL Server 2005 and up.

    The example of it which I always refer when I need to solve such a problem could be found in BOL: Recursive Queries Using Common Table Expressions

    1. USE AdventureWorks;
    2.  
    3. WITH DirectReports (ManagerID, EmployeeID, Title, DeptID, Level)
    4. AS
    5. (
    6. -- Anchor member definition
    7.     SELECT e.ManagerID, e.EmployeeID, e.Title, edh.DepartmentID,
    8.         0 AS Level
    9.     FROM HumanResources.Employee AS e
    10.     INNER JOIN HumanResources.EmployeeDepartmentHistory AS edh
    11.         ON e.EmployeeID = edh.EmployeeID AND edh.EndDate IS NULL
    12.     WHERE ManagerID IS NULL
    13.     UNION ALL
    14. -- Recursive member definition
    15.     SELECT e.ManagerID, e.EmployeeID, e.Title, edh.DepartmentID,
    16.         Level + 1
    17.     FROM HumanResources.Employee AS e
    18.     INNER JOIN HumanResources.EmployeeDepartmentHistory AS edh
    19.         ON e.EmployeeID = edh.EmployeeID AND edh.EndDate IS NULL
    20.     INNER JOIN DirectReports AS d
    21.         ON e.ManagerID = d.EmployeeID
    22. )
    23. -- Statement that executes the CTE
    24. SELECT ManagerID, EmployeeID, Title, Level
    25. FROM DirectReports
    26. INNER JOIN HumanResources.Department AS dp
    27.     ON DirectReports.DeptID = dp.DepartmentID
    28. WHERE dp.GroupName = N'Research and Development' OR Level = 0;

    Another slight variation of this same theme can be found in this MSDN thread
    ---------------------
    Below I describe few recent problems I solved using recursive CTE technique:

    Problem definition:


    I use SQL Server 2005 and I have a list of appr. 7700 items. In the list there is the complete stock quantity for each item and I also have the typical amount for each pallet. I need a list with all individual pallets and its stock

    Item stock amount/pallet
    100-001 2500 1000

    I need the list to look like this

    100-001 1000
    100-001 1000
    100-001 500

    Is this possible to do?

    It took me at least 20 minutes to come up with the following solution:

    1. declare @t table (item Varchar(10), Stock money, Pallet money)
    2.  
    3. insert into @t values('100-001',   2500,    1000)
    4. insert into @t values('100-002',   3000,    400)
    5. insert into @t values('100-003',   2000,    400)
    6.  
    7.  
    8. ;with cte_toInsert as
    9. (select Item, Pallet, Amount, Original_Stock, Stock - Amount as Stock
    10. from (select Item, Pallet, Stock as Original_Stock, Stock, case when Stock-Pallet > 0 then Pallet else Stock end as Amount from @t) X
    11. where Stock = 0 or (Stock >=0 and Amount > 0)
    12. union all select Item, Pallet, Amount, Original_Stock, Stock - Amount as Stock
    13. from (select Item, Pallet, Original_Stock, Stock,
    14. case when Stock-Pallet > 0 then Pallet else Stock end as Amount from cte_toInsert) X
    15. where (Stock = 0 and Amount > 0) OR Stock > 0 )
    16.  
    17. select * from cte_toInsert order by Item, Amount DESC OPTION (MAXRECURSION 10)

    Here is another interesting problem where I had to use Nikola's help to come up to the final solution:

    Given the initial list like this

    1. pk_rights   fk_rights   RightsName                                         RightsOrder Description
    2. ----------- ----------- -------------------------------------------------- ----------- ------------------------------
    3.  
    4. 1           NULL        Open Category Library                              0           Open Category Library
    5. 18          NULL        Open Portfolio Footnote Library                    1           Open Portfolio Footnote Library
    6. 2           1           Add Category Library                               0           Add Category Library
    7. 3           1           Delete Category Library                            1           Delete Category Library
    8. 11          2           Edit Category Library                              0           Edit Category Library
    9. 7           2           Add Category                                       2           Add Category
    10. 12          2           Add Category Language                              4           Add Category Language
    11. 14          2           Add Category Caption                               6           Add Category Caption
    12. 9           2           Delete Category                                    3           Delete Category
    13. 16          2           Delete Category Caption                            7           Delete Category Caption
    14. 17          2           Delete Category Language                           5           Delete Category Language
    15. 8           7           Edit Category                                      0           Edit Category
    16. 13          12          Edit Category Lanaguage                            0           Edit Category Lanaguage
    17. 15          14          EditCategory Caption                               0           EditCategory Caption

    I need the output to look like this.... the indents are for readability only but the rows must be ordered in this sequence.

    1. pk_rights   fk_rights   RightsName                                         RightsOrder Description
    2. ----------- ----------- -------------------------------------------------- ----------- ------------------------------
    3.  
    4. 1           NULL        Open Category Library                              0           Open Category Library
    5. 2           1              Add Category Library                               0        Add Category Library
    6. 11          2                 Edit Category Library                              0     Edit Category Library
    7. 3           1              Delete Category Library                            1        Delete Category Library
    8. 7           2              Add Category                                       2        Add Category
    9. 8           7                 Edit Category                                      0     Edit Category
    10. 9           2              Delete Category                                    3        Delete Category
    11. 12          2              Add Category Language                              4        Add Category Language
    12. 13          12                Edit Category Lanaguage                            0     Edit Category Lanaguage
    13. 17          2              Delete Cateogry Language                           5        Delete Cateogry Language
    14. 14          2              Add Category Caption                               6        Add Category Caption
    15. 15          14                EditCategory Caption                               0     EditCategory Caption
    16. 16          2              Delete Category Caption                            7        Delete Category Caption
    17. 18          NULL        Open Portfolio Footnote Library                    1           Open Portfolio Footnote Library

    Here is a solution by Nikola:

    1. ;with cte_t AS
    2.  (
    3.  SELECT T1.pk_rights,
    4.         t1.fk_rights,
    5.         T1.RightsName,
    6.         0 AS LEVEL,
    7.         T1.RightsOrder,
    8.         T1.Description AS ChildDescr,
    9.         CAST(RIGHT('000'+CAST(t1.RightsOrder AS VARCHAR(3)),2)  AS VARCHAR(50)) AS ro
    10.   FROM @Test T1
    11.  WHERE T1.fk_rights IS NULL
    12.  UNION all
    13.  SELECT T1.pk_rights,
    14.         T1.fk_rights,
    15.         T1.RightsName,
    16.         LEVEL + 1 AS LEVEL,
    17.         T1.RightsOrder,
    18.         T1.Description AS ChildDescr,
    19.         CAST(D.ro + '-' + RIGHT('000'+CAST(t1.RightsOrder AS VARCHAR(3)),2) AS VARCHAR(50)) AS ro
    20.   FROM @Test T1
    21.  INNER join cte_t D ON T1.fk_rights = D.pk_rights
    22. )
    23. SELECT cte.pk_rights,
    24.        cte.fk_rights,
    25.        LEFT(REPLICATE('   ',cte.LEVEL)+cte.RightsName,50) AS RightsName,
    26.        LEFT(REPLICATE('   ',cte.LEVEL)+CAST(RightsOrder AS VARCHAR(5)),15) AS RightsOrder,
    27.        LEFT(cte.ChildDescr,50) AS [Description]
    28.   FROM cte_t cte
    29.  ORDER BY ro

    Another simple problem was presented in this thread

    Find sum of products which have multiple level of categories and subcategories:

    1. -- creating test data
    2. declare @Test table (ID int identity(1,1), Valore int, IDRef int NULL)
    3. insert into @Test (Valore, IDRef) values (100,null), (20,1), (15,2),(-10,3), (200,null),(20,5),(335,6)
    4.  
    5. -- now creating recursive CTE
    6. select * from @Test
    7. ; with cte as (select ID as Root, ID as IDRef, Valore, ID from @Test where IdRef IS NULL
    8. union all
    9. select C.Root, T.IDRef, T.Valore, T.ID from cte C inner join @Test T on T.IDRef = C.ID)
    10.  
    11. -- Our final result
    12. select C.Root, SUM(Valore) as Total from cte C group by C.Root option (MaxRecursion 10)

    There is a similar problem discussed in this thread:

    and you can view very interesting solution in this blog by Itzik Ben-Gan.


    *** Remember, if you have a SQL related question, try our Microsoft SQL Server Programming forum or our Microsoft SQL Server Admin forum

    About the Author

    Naomi Nosonovsky, Senior Software Developer, has more than 15 years of enterprise experience in analysis, design, development and implementation of high-performance client/server data management solutions. She is a Personality of the Year for 2008, 2009, 2010 and 2011 at www.universalthread.com in .NET, SQL Server & VFP categories. She is also an All Star contributor/MCC Community Contributor at forums.asp.net and MSDN T-SQL forum.
    Social SitingsFacebookLinkedIndeliciousLTD RSS Feed
    7752 views
    InstapaperVote on HN

    10 comments

    Comment from: SQLDenis [Member] Email
    *****
    SQLDenis CTEs are one of the more useful but underused enhancements to T-SQL. More developers should start using them instead of derived tables
    09/14/09 @ 10:08
    Comment from: Web Design [Visitor] Email · http://www.webdesignexpert.me
    ****-
    Web Design I wonder if CTE's (like the CTE used in solving the pallet problem) would become more readable and understandable if the logic moved to code other than SQL. SQL is not well suited to be a programming language.
    09/15/09 @ 00:28
    Comment from: Naomi Nosonovsky [Member]
    Naomi Nosonovsky Yes, I agree, that in many cases the processing can be also done on the client. I guess in every case it depends on many different factors what would be preferable.
    09/15/09 @ 07:04
    Comment from: Bryian Tan [Visitor] · http://blog.ysatech.com
    ****-
    Bryian Tan nice post.
    10/13/09 @ 15:16
    Comment from: Alfred Brevell [Visitor] · http://aaliyah.wblogin.com/
    Alfred Brevell Greetings I recently finished reading through your blog and I’m very impressed. I do have a couple queries for you personally however. Do you consider you’re thinking about doing a follow-up publishing about this? Will you be planning to keep bringing up-to-date also?
    11/18/10 @ 23:53
    Comment from: Berneice Celentano [Visitor]
    Berneice Celentano Most what i read online is trash and copy paste but i think you offer something different. Bravo.
    11/25/10 @ 12:14
    Comment from: Elizabeth Beehler [Visitor] · http://www.polinios.com
    Elizabeth Beehler Most of the times i visit a blog I notice that most blogs are amateurish.On the other hand,I could honestly say that you writting is decent and your website solid.
    11/25/10 @ 14:19
    Comment from: Jeremy [Visitor]
    Jeremy Great post full of useful tips! My site is fairly new and I am also having a hard time getting my readers to leave comments. Analytics shows they are coming to the site but I have a feeling nobody wants to be first.
    12/02/10 @ 02:44
    Comment from: Bob Pusateri [Visitor] · http://www.bobpusateri.com
    Bob Pusateri Thanks for the great post, Naomi, and also for submitting this to T-SQL Tuesday!
    05/10/11 @ 10:13
    Comment from: Samaniego7766 [Visitor]
    Samaniego7766 Quite a good read, I always enjoy informational pieces. I hope to see more like it soon!
    09/06/12 @ 18:36

    Leave a comment


    Your email address will not be revealed on this site.

    To mislead the spambots.

    Your URL will be displayed.
    (Line breaks become <br />)
    (Name, email & website)
    (Allow users to contact you through a message form (your email will not be revealed.)