问题
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