views:

47

answers:

1

Hi

Based on the following table

ID    Path       
---------------------------------------
1  \\Root
2  \\Root\Node0
3  \\Root\Node0\Node1
4  \\Root\Node0\Node2
5  \\Root\Node3
6  \\Root\Node3\Node4
7  \\Root\Node5
...
N  \\Root\Node5\Node6\Node7\Node8\Node9\Node10

so on...

There are around 1000 rows in this table. I want to display individual nodes in separate columns. Maximum columns to be displayed 5 (i.e. node till 5 level deep). So the output will look as below

ID    Path           Level 0   Level 1  Level 2  Level 3  Level 4  Level 5
----------------------------------------------------------------------------------------
1  \\Root                    Root      Null     Null     Null     Null     Null
2  \\Root\Node0              Root      Node 0   Null     Null     Null     Null
3  \\Root\Node0\Node1        Root      Node 0   Node 1   Null     Null     Null
4  \\Root\Node0\Node2        Root      Node 0   Node 2   Null     Null     Null
5  \\Root\Node3              Root      Node 3   Null     Null     Null     Null
6  \\Root\Node3\Node4        Root      Node 3   Node 4   Null     Null     Null
7  \\Root\Node5              Root      Node 5   Null     Null     Null     Null
...
N  (see in above table)      Root      Node 5   Node 6   Node 7   Node 8 Node 9

The only way I can think of is to open a cursor, loop through each row and perform a string split, just fetch the first 5 nodes and then insert into a temp table.

Please suggest.

Thanks

A: 

What you need is a table-valued split function akin to the following:

CREATE FUNCTION [dbo].[udf_Split] (@DelimitedList nvarchar(max), @Delimiter nvarchar(2) = ',')
RETURNS @SplitResults TABLE (Position int NOT NULL PRIMARY KEY, Value nvarchar(max))
AS
Begin
    Declare @DelimiterLength int
    Set @DelimiterLength = DataLength(@Delimiter) / 2

    If Left(@DelimitedList, @DelimiterLength) <> @Delimiter
        Set @DelimitedList = @Delimiter + @DelimitedList

    If Right(@DelimitedList, @DelimiterLength) <> @Delimiter
        Set @DelimitedList = @DelimitedList + @Delimiter

    Insert @SplitResults(Position, Value)
    Select CharIndex(@Delimiter, A.list, N.Value) + @DelimiterLength            
        , Substring (
                    A.List
                    , CharIndex(@Delimiter, A.list, N.Value) + @DelimiterLength         
                    , CharIndex(@Delimiter, A.list, N.Value + 1)                            
                        - ( CharIndex(@Delimiter, A.list, N.Value) + @DelimiterLength ) 
                    )
    From dbo.Numbers As N
        Cross Join (Select @DelimitedList As list) As A
    Where N.Value > 0
        And N.Value < LEN(A.list)
        And Substring(A.list, N.Value, @DelimiterLength) = @Delimiter
    Order By N.Value

    Return
End

This function relies on the existence of a Numbers table which contains a sequential list of integers. Now, you could take your original data and do something like:

With TableData As
    (
    Select 1 As Id, '\\Root' As [Path]
    Union Select All 2, '\\Root\Node0'
    Union Select All 3, '\\Root\Node0\Node1'
    Union Select All 4, '\\Root\Node0\Node2'
    Union Select All 5, '\\Root\Node3'
    Union Select All 6, '\\Root\Node3\Node4'
    Union Select All 7, '\\Root\Node5'
    )
    , SplitData As
    (
    Select T.Id, T.[Path], S.Value
        , Row_Number() Over ( Partition By T.Id Order By S.Position ) As Level
    From TableData As T
        Cross Apply dbo.udf_Split( (Substring(T.[Path],2,Len(T.[Path])) + '\') , '\') As S
    )
Select Id, [Path]
    , Min( Case When Level = 1 Then S.Value End ) As Level0
    , Min( Case When Level = 2 Then S.Value End ) As Level1
    , Min( Case When Level = 3 Then S.Value End ) As Level2
    , Min( Case When Level = 4 Then S.Value End ) As Level3
    , Min( Case When Level = 5 Then S.Value End ) As Level4
From SplitData As S
Group By Id, [Path]
Thomas
Thanks Thomas. So should i create a temp Numbers table? Also how many rows should the Numbers table contain?
stackoverflowuser
@stackoverflowuser - There is no reason the table could not be permanent. There are many uses for a Numbers table and this is just one of them. If you notice in the Split function, it filters on the length of the string, so the numbers table should be at least as large as the largest string. However, 10K rows will take up little space and should be more than enough for what you need.
Thomas
Hi Thomas, I am unable to pass "Path" (column from input table) as input to dbo.udf_Split. It gives me invalid column error. Also where does "Element" column come from? Thanks.
stackoverflowuser
@stackoverflowuser - Actually, the main oversight is that I typed `Cross Join` instead of `Cross Apply`.
Thomas
@stackoverflowuser - Ran some tests and have adjust the answer. As I mentioned earlier, I should have used `Cross Apply`. In addition, in order to get the split function to play nicely, we need to append a delimiter and remove the extra leading delimiter which I've done to my post.
Thomas
@Thomas: Thanks. I applied the changes. Now i am getting "The multi-part identifier "S.Value" could not be bound". Any idea?
stackoverflowuser
@stackoverflowuser - Sounds like you are missing an alias somewhere.
Thomas
@stackoverflowuser - I found another bug. I posted the code I used to run my test and it seems to be working correctly.
Thomas
@Thomas: That works !! Thanks.
stackoverflowuser