Limiting recursion to certain level - Duplicate rows

馋奶兔 提交于 2019-12-24 03:23:31

问题


To start this isn't a duplicate of my other question with the same name, I just couldn't think of a better name for this one!

I have a SQL table named Player and another called Unit.

  • Each Player MUST belong to a team via a foreign key UnitID.
  • Each Unit can belong to another Team via a recursive field ParentUnitID.
  • A ParentUnit CAN be a ParentUnit (infinite recursion), but a Player can only belong to one Team.
  • A Unit may have many children

So it could be (top down)...

  • TeamA (is the top level)
  • TeamB (belongs to ^^)
  • TeamC (belongs to ^^)
  • TeamD (belongs to ^^)
  • Player_1 (belongs to ^^)

My question is, if I'm given a Player's PlayerID (the PK for that table), what is the best way to get a specific team?

See my SQLFiddle for the data, structure and a query: http://sqlfiddle.com/#!3/78965/3

With my data (from the fiddle), I want to be able to get every player under "TeamB", but then "Player4"'s top unit should be "TeamC". In order to do that all I want to pass in is the PlayerID and the ID for "TeamB". So I'm saying "get all players and top units under TeamB and then filtering out all Players except Player4.

EDIT: I believe the above paragraph should read: With my data (from the fiddle), I want to be able to establish the top team(s) that plays below "TeamB". For each top team below "TeamB" I then want to establish all players that play for or below that team. I then want the ability to limit the list of players to one or more specific players.

As you can see in SQLFiddle I get back multiple rows for each player, I'm sure it's a quick fix, but I can't figure it out... That's what I'm going for in my fiddle, but it's, well, uh, a bit fiddly... :)

Edit: More Information:

OK, so if imagine this is a stored procedure.

  • I pass in PlayerIDs 1,2,3,4
  • I pass in UnitID 2

I would expect the returned data to look something like

Player3, TeamC

Only Player3 is returned as it is the only player which is a descendant of TeamB (ID 2), and TeamC is returned as it is the highest level unit (below UnitID 2) which Player3 belongs to.

If I instead passed in:

  • I pass in PlayerIDs 1,2,3,4
  • I pass in UnitID 6

I would expect

Player1, Team2
Player2, Team2

回答1:


This answer has been completely rewritten. The original did not quite work in all circumstances

I had to change the CTE to represent the full Unit hierarchy for every Unit as a possible root (top unit). It allows a true hierarchy with multiple children per Unit.

I extended the sample data in this SQL Fiddle to have a player assigned to both units 11 and 12. It properly returns the correct row for each of 3 players that play for a Unit at some level beneath Unit 1.

The "root" Unit ID and the list of player IDs is conveniently in the outermost WHERE clause at the bottom, making it easy to change the IDs as needed.

with UnitCTE as (
  select u.UnitID,
         u.Designation UnitDesignation,
         u.ParentUnitID as ParentUnitID,
         p.Designation as ParentUnitDesignation,
         u.UnitID TopUnitID,
         u.Designation TopUnitDesignation,
         1 as TeamLevel
    from Unit u
    left outer join Unit p
      on u.ParentUnitId = p.UnitID
  union all
  select t.UnitID,
         t.Designation UnitDesignation,
         c.UnitID as ParentUnitID,
         c.UnitDesignation as ParentUnitDesignation,
         c.TopUnitID,
         c.TopUnitDesignation,
         TeamLevel+1 as TeamLevel
    from Unit t
    join UnitCTE c
      on t.ParentUnitID = c.UnitID
)
select p.PlayerID,
       p.Designation,
       t1.*
  from UnitCTE t1
  join UnitCTE t2
    on t2.TopUnitID = t1.UnitID
   and t2.TopUnitID = t1.TopUnitID
  join Player p
    on p.UnitID = t2.UnitID
 where t1.ParentUnitID = 1
   and playerID in (1,2,3,4,5,6)

Here is a slightly optimized version that has the Unit ID criteria embedded in the CTE. The CTE only computes hierarchies rooted on Units where Parent ID is the chosen Unit ID (1 in this case)

with UnitCTE as (
  select u.UnitID,
         u.Designation UnitDesignation,
         u.ParentUnitID as ParentUnitID,
         p.Designation as ParentUnitDesignation,
         u.UnitID TopUnitID,
         u.Designation TopUnitDesignation,
         1 as TeamLevel
    from Unit u
    left outer join Unit p
      on u.ParentUnitId = p.UnitID
   where u.ParentUnitID = 1
  union all
  select t.UnitID,
         t.Designation UnitDesignation,
         c.UnitID as ParentUnitID,
         c.UnitDesignation as ParentUnitDesignation,
         c.TopUnitID,
         c.TopUnitDesignation,
         TeamLevel+1 as TeamLevel
    from Unit t
    join UnitCTE c
      on t.ParentUnitID = c.UnitID
)
select p.PlayerID,
       p.Designation,
       t1.*
  from UnitCTE t1
  join UnitCTE t2
    on t2.TopUnitID = t1.UnitID
  join Player p
    on p.UnitID = t2.UnitID
 where playerID in (1,2,3,4,5,6)



Here is my original answer. It only works if the Unit Hierarchy is constrained to allow only one child per Unit. The SQL Fiddle example in the question has 3 children for Unit 1, so it falsely returns multiple rows for Players 3, 5 and 6 if run against Unit 1

Here is a SQL Fiddle that demonstrates the problem.

with UnitCTE as
  select UnitID,
         Designation UnitDesignation,
         ParentUnitID as ParentUnitID,
         cast(null as varchar(50)) as ParentUnitDesignation,
         UnitID TopUnitID,
         Designation TopUnitDesignation,
         1 as TeamLevel
    from Unit
   where ParentUnitID is null
  union all
  select t.UnitID,
         t.Designation UnitDesignation,
         c.UnitID,
         c.UnitDesignation,
         c.TopUnitID,
         c.TopUnitDesignation,
         TeamLevel+1 as TeamLevel
    from Unit t
    join UnitCTE c
      on t.ParentUnitID = c.UnitID
)
select p.PlayerID,
       p.Designation,
       t2.*
  from Player p
  join UnitCTE t1
    on p.UnitID = t1.UnitID
  join UnitCTE t2
    on t2.TopUnitID = t1.TopUnitID
   and t1.TeamLevel >= t2.TeamLevel
  join UnitCTE t3
    on t3.TopUnitID = t1.TopUnitID
   and t2.TeamLevel = t3.TeamLevel+1
 where t3.UnitID = 2
   and playerID in (1,2,3,4)



回答2:


with UnitCTE as (
  select UnitID,
         Designation,
         ParentUnitID as ParentUnitID,
         cast(null as varchar(50)) as ParentUnitDesignation,
         UnitID TopUnitID,
         Designation TopUnitDesignation,
         1 as TeamLevel
    from Unit
   where ParentUnitID is null
  union all
  select t.UnitID,
         t.Designation,
         c.UnitID,
         c.Designation,
         c.TopUnitID,
         c.TopUnitDesignation,
         TeamLevel+1 as TeamLevel
    from Unit t
    join UnitCTE c
      on t.ParentUnitID = c.UnitID
      --WHERE t.UnitID = 1
),
x AS (
select Player.PlayerID,
       pDesignation = Player.Designation, t1.*,
       rn = ROW_NUMBER() OVER (PARTITION BY Player.PlayerID ORDER BY Player.Designation)
  from Player
  join UnitCTE t1
    on Player.UnitID = t1.UnitID
  join UnitCTE t2
    on t1.TopUnitID = t2.TopUnitID
   and t2.TeamLevel=2
)
SELECT * FROM x
WHERE rn = 1
   ORDER BY TeamLevel


来源:https://stackoverflow.com/questions/11512805/limiting-recursion-to-certain-level-duplicate-rows

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!