SQL Server 2005 recursive query with loops in data - is it possible?

前端 未结 10 1758
野趣味
野趣味 2020-12-06 03:29

I\'ve got a standard boss/subordinate employee table. I need to select a boss (specified by ID) and all his subordinates (and their subrodinates, etc). Unfortunately the rea

相关标签:
10条回答
  • 2020-12-06 04:20

    The preferrable solution is to clean up the data and to make sure you do not have any loops in the future - that can be accomplished with a trigger or a UDF wrapped in a check constraint.

    However, you can use a multi statement UDF as I demonstrated here: Avoiding infinite loops. Part One

    You can add a NOT IN() clause in the join to filter out the cycles.

    0 讨论(0)
  • 2020-12-06 04:23

    I can think of two approaches.

    1) Produce more rows than you want, but include a check to make sure it does not recurse too deep. Then remove duplicate User records.

    2) Use a string to hold the Users already visited. Like the not in subquery idea that didn't work.

    Approach 1:

    ; with TooMuchHierarchy as (
        select "User_ID"
            , Manager_ID 
            , 0 as Depth
        from "User" 
        WHERE "User_ID" = @UserID
        union all
        select U."User_ID"
            , U.Manager_ID
            , M.Depth + 1 as Depth
        from TooMuchHierarchy M
        inner join "User" U 
            on U.Manager_ID = M."user_id"
        where Depth < 100) -- Warning MAGIC NUMBER!!
    , AddMaxDepth as (
        select "User_ID"
            , Manager_id
            , Depth
            , max(depth) over (partition by "User_ID") as MaxDepth
        from TooMuchHierarchy)
    select "user_id", Manager_Id 
    from AddMaxDepth
    where Depth = MaxDepth
    

    The line where Depth < 100 is what keeps you from getting the max recursion error. Make this number smaller, and less records will be produced that need to be thrown away. Make it too small and employees won't be returned, so make sure it is at least as large as the depth of the org chart being stored. Bit of a maintence nightmare as the company grows. If it needs to be bigger, then add option (maxrecursion ... number ...) to whole thing to allow more recursion.

    Approach 2:

    ; with Hierarchy as (
        select "User_ID"
            , Manager_ID 
            , '#' + cast("user_id" as varchar(max)) + '#' as user_id_list
        from "User" 
        WHERE "User_ID" = @UserID
        union all
        select U."User_ID"
            , U.Manager_ID
            , M.user_id_list + '#' + cast(U."user_id" as varchar(max)) + '#' as user_id_list
        from Hierarchy M
        inner join "User" U 
            on U.Manager_ID = M."user_id"
        where user_id_list not like '%#' + cast(U."User_id" as varchar(max)) + '#%')
    select "user_id", Manager_Id 
    from Hierarchy
    
    0 讨论(0)
  • 2020-12-06 04:25

    I know it has been a while but thought I should share my experience as I tried every single solution and here is a summary of my findings (an maybe this post?):

    • Adding a column with the current path did work but had a performance hit so not an option for me.
    • I could not find a way to do it using CTE.
    • I wrote a recursive SQL function which adds employeeIds to a table. To get around the circular referencing, there is a check to make sure no duplicate IDs are added to the table. The performance was average but was not desirable.

    Having done all of that, I came up with the idea of dumping the whole subset of [eligible] employees to code (C#) and filter them there using a recursive method. Then I wrote the filtered list of employees to a datatable and export it to my stored procedure as a temp table. To my disbelief, this proved to be the fastest and most flexible method for both small and relatively large tables (I tried tables of up to 35,000 rows).

    0 讨论(0)
  • 2020-12-06 04:29

    This is the code I used on a project to chase up and down hierarchical relationship trees.

    User defined function to capture subordinates:

    CREATE FUNCTION fn_UserSubordinates(@User_ID INT)
    RETURNS @SubordinateUsers TABLE (User_ID INT, Distance INT) AS BEGIN
        IF @User_ID IS NULL
            RETURN
    
        INSERT INTO @SubordinateUsers (User_ID, Distance) VALUES ( @User_ID, 0)
    
        DECLARE @Distance INT, @Finished BIT
        SELECT @Distance = 1, @Finished = 0
    
        WHILE @Finished = 0
        BEGIN
            INSERT INTO @SubordinateUsers
                SELECT S.User_ID, @Distance
                    FROM Users AS S
                    JOIN @SubordinateUsers AS C
                        ON C.User_ID = S.Manager_ID
                    LEFT JOIN @SubordinateUsers AS C2
                        ON C2.User_ID = S.User_ID
                    WHERE C2.User_ID IS NULL
            IF @@RowCount = 0
                SET @Finished = 1
    
            SET @Distance = @Distance + 1
        END
    
        RETURN
    END
    

    User defined function to capture managers:

    CREATE FUNCTION fn_UserManagers(@User_ID INT)
    RETURNS @User TABLE (User_ID INT, Distance INT) AS BEGIN
        IF @User_ID IS NULL
            RETURN
    
        DECLARE @Manager_ID INT
    
        SELECT @Manager_ID = Manager_ID
        FROM UserClasses WITH (NOLOCK)
        WHERE User_ID = @User_ID
    
        INSERT INTO @UserClasses (User_ID, Distance)
            SELECT User_ID, Distance + 1
            FROM dbo.fn_UserManagers(@Manager_ID)
    
        INSERT INTO @User (User_ID, Distance) VALUES (@User_ID, 0)
    
        RETURN
    END
    
    0 讨论(0)
提交回复
热议问题