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

前端 未结 10 1757
野趣味
野趣味 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:07

    Not a generic solution, but might work for your case: in your select query modify this:

    select a.[User_ID], a.[Manager_ID] from [User] a join UserTbl b on (a.[Manager_ID]=b.[User_ID])
    

    to become:

    select a.[User_ID], a.[Manager_ID] from [User] a join UserTbl b on (a.[Manager_ID]=b.[User_ID]) 
       and a.[User_ID] <> @UserID
    
    0 讨论(0)
  • 2020-12-06 04:07

    You don't have to do it recursively. It can be done in a WHILE loop. I guarantee it will be quicker: well it has been for me every time I've done timings on the two techniques. This sounds inefficient but it isn't since the number of loops is the recursion level. At each iteration you can check for looping and correct where it happens. You can also put a constraint on the temporary table to fire an error if looping occurs, though you seem to prefer something that deals with looping more elegantly. You can also trigger an error when the while loop iterates over a certain number of levels (to catch an undetected loop? - oh boy, it sometimes happens.

    The trick is to insert repeatedly into a temporary table (which is primed with the root entries), including a column with the current iteration number, and doing an inner join between the most recent results in the temporary table and the child entries in the original table. Just break out of the loop when @@rowcount=0! Simple eh?

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

    this will work for the initial recursive link, but might not work for longer links

    DECLARE @Table TABLE(
            ID INT,
            PARENTID INT
    )
    
    INSERT INTO @Table (ID,PARENTID) SELECT 1, 2
    
    INSERT INTO @Table (ID,PARENTID) SELECT 2, 1
    
    INSERT INTO @Table (ID,PARENTID) SELECT 3, 1
    
    INSERT INTO @Table (ID,PARENTID) SELECT 4, 3
    
    INSERT INTO @Table (ID,PARENTID) SELECT 5, 2
    
    
    SELECT * FROM @Table
    
    DECLARE @ID INT
    
    SELECT @ID = 1
    
    ;WITH boss (ID,PARENTID) AS (
        SELECT  ID,
                PARENTID
        FROM    @Table
        WHERE   PARENTID = @ID
    ),
     bossChild (ID,PARENTID) AS (
        SELECT  ID,
                PARENTID
        FROM    boss
        UNION ALL
        SELECT  t.ID,
                t.PARENTID
        FROM    @Table t INNER JOIN
                bossChild b ON t.PARENTID = b.ID
        WHERE   t.ID NOT IN (SELECT PARENTID FROM boss)
    )
    SELECT  *
    FROM    bossChild
    OPTION (MAXRECURSION 0)
    

    what i would recomend is to use a while loop, and only insert links into temp table if the id does not already exist, thus removing endless loops.

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

    You need a some method to prevent your recursive query from adding User ID's already in the set. However, as sub-queries and double mentions of the recursive table are not allowed (thank you van) you need another solution to remove the users already in the list.

    The solution is to use EXCEPT to remove these rows. This should work according to the manual. Multiple recursive statements linked with union-type operators are allowed. Removing the users already in the list means that after a certain number of iterations the recursive result set returns empty and the recursion stops.

    with UserTbl as -- Selects an employee and his subordinates.
    (
        select a.[User_ID], a.[Manager_ID] from [User] a WHERE [User_ID] = @UserID
        union all
        (
          select a.[User_ID], a.[Manager_ID] 
            from [User] a join UserTbl b on (a.[Manager_ID]=b.[User_ID])
            where a.[User_ID] not in (select [User_ID] from UserTbl)
          EXCEPT
            select a.[User_ID], a.[Manager_ID] from UserTbl a 
         )
    )
    select * from UserTbl;
    

    The other option is to hardcode a level variable that will stop the query after a fixed number of iterations or use the MAXRECURSION query option hint, but I guess that is not what you want.

    0 讨论(0)
  • I know you asked this question a while ago, but here is a solution that may work for detecting infinite recursive loops. I generate a path and I checked in the CTE condition if the USER ID is in the path, and if it is it wont process it again. Hope this helps.

    Jose

    DECLARE @Table TABLE(
        USER_ID INT,
        MANAGER_ID INT )
    INSERT INTO @Table (USER_ID,MANAGER_ID) SELECT 1, 2
    INSERT INTO @Table (USER_ID,MANAGER_ID) SELECT 2, 1
    INSERT INTO @Table (USER_ID,MANAGER_ID) SELECT 3, 1
    INSERT INTO @Table (USER_ID,MANAGER_ID) SELECT 4, 3
    INSERT INTO @Table (USER_ID,MANAGER_ID) SELECT 5, 2
    
    DECLARE @UserID INT
    SELECT @UserID = 1
    
    ;with
    UserTbl as -- Selects an employee and his subordinates.
    (
        select 
            '/'+cast( a.USER_ID as varchar(max)) as [path],
            a.[User_ID], 
            a.[Manager_ID] 
        from @Table a 
        where [User_ID] = @UserID
        union all
        select
            b.[path] +'/'+ cast( a.USER_ID as varchar(max)) as [path],
            a.[User_ID], 
            a.[Manager_ID] 
        from @Table a 
        inner join UserTbl b 
            on (a.[Manager_ID]=b.[User_ID])
        where charindex('/'+cast( a.USER_ID as varchar(max))+'/',[path]) = 0
    )
    select * from UserTbl
    
    0 讨论(0)
  • 2020-12-06 04:20

    basicaly if you have loops like this in data you'll have to do the retreival logic by yourself. you could use one cte to get only subordinates and other to get bosses.

    another idea is to have a dummy row as a boss to both company owners so they wouldn't be each others bosses which is ridiculous. this is my prefferd option.

    0 讨论(0)
提交回复
热议问题