views:

51

answers:

2

Hello. I am trying to perform what I believe is a difficult recursion using a CTE is SQL Server 2008. I can't seem to wrap my head around this one.

In the below examples you can assume a fixed depth of 3...nothing will ever be lower than that. In real life, the depth is "deeper" but still fixed. In the example I tried to simplify it some.

My input data is like the below.

ID     PARENT_ID       NAME          DEPTH
------------------------------------------
1      NULL            A             1
2      1               B             2
3      2               C             3
4      1               D             2

The output of my CTE should be the following table.

LEVEL1_ID    LEVEL2_ID    LEVEL3_ID    LEVEL1_NAME    LEVEL2_NAME    LEVEL3_NAME
--------------------------------------------------------------------------------
1            NULL         NULL         A              NULL           NULL
1            2            NULL         A              B              NULL
1            2            3            A              B              C
1            4            NULL         A              D              NULL

If I can get the ID columns in the output I can certainly map to names in a lookup table.

I am open to other ways of accomplishing this as well, including using SSIS.

+3  A: 

Not really all that hard to do:

;WITH cte AS
(
    SELECT CAST('/' + Name AS VARCHAR(50)) as 'CteName', ID
    FROM dbo.YourTable
    WHERE parent_id IS NULL

    UNION ALL

    SELECT CAST(cte.CteName + '/' + Name AS VARCHAR(50)), t.ID
    FROM dbo.YourTable t
    INNER JOIN cte ON t.parent_id = cte.id
)
SELECT cteName FROM cte
ORDER BY ID

Gives me an output of:

/A
/A/B
/A/B/C
/A/D

As a side-note: the "depth" could be easily computed by the CTE and you don't necessarily need to store that in your table (see the Level column I've added):

;WITH cte AS
(
    SELECT 
       CAST('/' + Name AS VARCHAR(50)) as 'CteName', ID, 
       1 AS 'Level'
    FROM dbo.YourTable
    WHERE parent_id IS NULL

    UNION ALL

    SELECT 
       CAST(cte.CteName + '/' + Name AS VARCHAR(50)), t.ID,
       cte.Level + 1 AS 'Level'
    FROM dbo.YourTable t
    INNER JOIN cte ON t.parent_id = cte.id
)
SELECT cteName FROM cte
ORDER BY Level, ID
marc_s
this is awesome. thanks! I changed the question a little but this is excellent. I can certainly use this example.
thomas
Great marc_s! So quick!!!!
Ismail
With this code you don't have the problem of joining the levels multiple times? For example during the 1st run of the recursive run you will join the levels 1 with levels 2, during the 2nd run you will join again the levels 1 with levels 2, and levels 2 with levels 3, and so on.. the duplicate rows are "removed" b the UNION operator, but it will do a lot of duplicate joins.
munissor
@munissor: no, SQL Server will handle this specific version of the CTE as a recursive CTE (UNION ALL is the required keyword for that) and will do all the necessary work needed. No level is joined multiple times.
marc_s
You're right, the union all will preserve duplicate values, if present.
munissor
A: 

I don't remember of you can do a subquery in a cte.

I don't have a copy of sql server here but you can try with this code:

WITH cte(id, path, level)
AS
(
    SELECT id, '/' + name, level
    FROM yourtable
    WHERE level = 1

    UNION ALL

    SELECT y.id, c.name + '/' + y.name, y.level
    FROM yourtable y INNER JOIN
    cte c ON c.id = y.parent_id
    WHERE level = (SELECT max(level)+1 from cte)
)
SELECT path from cte
munissor
I only get a ton of errors: Msg 207, Level 16, State 1, Line 6Invalid column name 'level'.Msg 207, Level 16, State 1, Line 4Invalid column name 'level'.Msg 253, Level 16, State 1, Line 1Recursive member of a common table expression 'cte' has multiple recursive references.
marc_s
Ok so you can't use a subquery ;)
munissor