SQL - Assignment in CASE inside a SELECT in a Recursive Query - sql

I'm creating a SQL Server 2008 query that would output the list of employees in a company along with the team they are on with an additional column.
Example of the org tree:
Level 0: CEO
Level 1: A, B, and C
Level 2:
For A:1,2,3
For B:4,5,6
For C:7,8,9
In my resulting set, I should see three columns -- name, level (of the tree), and team. For 1,2, and 3, I'd see 'A' as their team and 2 as the level. For 4,5, and 6, 'B' and 2 for the level and so on.
I'm using a recursive query to navigate the tree (no problems there), but since I need to "carry" the team name down the query (in case there's a level 8 -- it should still show the person in level 1 they report to), I'm doing this:
(...)
UNION ALL
-- Recursive Member Definition
-- in here level increments one each time, and the team should output the child
-- of the top manager
SELECT A.treenodeid, A.parentnodeid, A.email, LEVEL+1, team =
CASE LEVEL
When 1 then SET #salead = A.Email
Else #salead
END
FROM XX as A
INNER JOIN TeamsTable as B on A.parentnodeid = b.treenodeID
Since I'm trying to use a CASE to check if the level is 1 (to update the team name to whatever the team lead's email name is), SQL keeps saying that in the case I have "Incorrect syntax near SET".
Is it possible to do this sort of assignment in a CASE? I've looked around and haven't found if this can work with my recursive case.
Here's all the query (assuming that the root is 'JohnSmith'):
WITH TeamsTable (treenodeid, parentnodeid, email, Level, team)
AS
(
-- Anchor - Level starts with 0, and the team is empty for the top manager
SELECT treenodeid,parentnodeid,email,0,''
FROM XX WHERE email = 'JohnSmith'
UNION ALL
-- Recursive Member Definition - in here level increments one each time, and the team should output the child of the top manager
SELECT
A.treenodeid, A.parentnodeid, A.email, LEVEL+1, team =
CASE LEVEL
When 1 then SET #salead = A.Email
Else #salead
END
FROM XX as A
INNER JOIN TeamsTable as B on A.parentnodeid = b.treenodeID
)
-- Statement that executes the CTE
SELECT *
FROM TeamsTable
Thanks a lot, guys!

Why do you need the variable at all? I don't see it being used subsequently

I would approach this problem from the bottom up, not from the top down.
You've not said what teams A, B, C or CEO should be in, so I've made that up.
I've also included the sample data in a usable form:
create table Org (
ID int not null,
Name varchar(10) not null,
ParentID int null
)
go
insert into Org (ID,Name,ParentID) values
(1,'CEO',null),
(2,'A',1),
(3,'B',1),
(4,'C',1),
(5,'1',2),
(6,'2',2),
(7,'3',2),
(8,'4',3),
(9,'5',3),
(10,'6',3),
(11,'7',4),
(12,'8',4),
(13,'9',4)
Query:
;With AllPeople as (
select ID,ID as LastParentID,ParentID as NextParentID, CASE WHEN ParentID is null THEN 0 ELSE 1 END as Level
from Org
union all
select ap.ID,ap.NextParentID,o.ParentID,Level + 1
from
AllPeople ap
inner join
Org o
on
ap.NextParentID = o.ID and
o.ParentID is not null
), Roots as (
select ID from Org where ParentID is null
), RootedPeople as (
select * from AllPeople where NextParentID is null or NextParentID in (select ID from Roots)
), Names as (
select
oself.Name,
oteam.Name as Team,
Level
from
RootedPeople rp
inner join
Org oself on rp.ID = oself.ID
left join
Org oteam on rp.LastParentID = oteam.ID
)
select * from Names
Result:
Name Team Level
---------- ---------- -----------
CEO CEO 0
A A 1
B B 1
C C 1
9 C 2
8 C 2
7 C 2
6 B 2
5 B 2
4 B 2
3 A 2
2 A 2
1 A 2
Explanation of the CTEs:
AllPeople is a recursive query that climbs up the organisation tree until it reaches the root. We use two columns (LastParentID and NextParentID) to track two levels of the hierarchy - because, apparently, once we reach the root, we want the level before that.
Roots finds all of the people who don't have a parent. It's how we identify the rows from AllPeople that were complete, in,
RootedPeople where we find rows which never successfully found any parents, or where the recursion had reached the top of the tree.
Names and finally we join back to the Org table to assign names to individuals and teams. This one isn't necessary - it could be the final query by itself.
Note also that, due to the recursive way the AllPeople CTE is built, we calculate the levels as we go - every time we recurse, we add one to the Level that this row represents.

Related

SQL - first common ancestor in hierarachy

I am attempting to use a CTE to find the first common ancestor in a hierarchy for all values in a source table. I am able to return the entire hierarchy, but cannot seem to find a way to get the first common ancestor.
The ultimate goal is to update the source table to use only common ancestors.
Source table:
Type_Id Id Parent_Id Group_Id
A 3 2 1
A 4 2 1
A 5 4 1
After the CTE the table I get is:
Type_Id Id Child_Id Parent_Id Group_Id Level
A 3 3 2 1 1
A 4 4 2 1 1
A 5 5 4 1 1
A 5 4 2 1 2
What I'm looking to do is update the id of the source so that for each type and group combination, the new id is to be the first common parent. In this case, it would be set all ids to 2 as that's the first common entry.
You can try to use cte recursive self-join by your logic.
;WITH CTE as (
SELECT Type_Id,Id,id Child_Id,Parent_Id,Group_Id,1 Level
FROM T
UNION ALL
SELECT c.Type_Id,c.Id,t.id Child_Id,t.Parent_Id,t.Group_Id,c.Level+1
FROM CTE c
INNER JOIN T t
ON c.Parent_Id = t.ID AND c.Type_Id = t.Type_Id
)
SELECT *
FROM CTE
sqlfiddle
By building a list of all ancestor relationships (including self) and identifying the depth of each node from the root, you can then query all ancestors for the N selected nodes of interest. Any ancestors occurring N times are candidate common ancestors. The one with the deepest depth is the answer.
The following is what I came up with using simplified Node data containing only Id and ParentId. (The initial CTE was adapted from D-Shih's post.)
;WITH Ancestry as (
-- Recursive list of ancestors (including self) for each node
SELECT ChildId = N.Id, AncestorLevel = 0, AncestorId = N.Id, AncestorParentId = N.ParentId
FROM #Node N
UNION ALL
SELECT C.ChildId, AncestorLevel = C.AncestorLevel + 1, AncestorId = P.Id, AncestorParentId = P.ParentId
FROM Ancestry C
INNER JOIN #Node P ON P.Id = C.AncestorParentId
),
AncestryDepth AS (
-- Calculate depth from root (There may be a quicker way)
SELECT *, Depth = COUNT(*) OVER(PARTITION BY ChildId) - AncestorLevel
FROM Ancestry
),
Answer AS (
-- Answer is deepest node that is an ancestor of all selected nodes
SELECT TOP 1 Answer = A.AncestorId
FROM #Selected S
JOIN AncestryDepth A ON A.ChildId = S.Id
GROUP BY A.AncestorId, A.Depth
HAVING COUNT(*) = (SELECT COUNT(*) FROM #Selected) -- Ancestor covers all
ORDER BY A.Depth DESC -- Deepest
)
SELECT *
FROM Answer
There may be room for improvement, possibly a more direct calculation of depth, but this appears to give the correct result.
See this db<>fiddle for a working demo with data and several test scenarios.

SQL Determine the teams having same members

I have an interesting problem.
I've some teams with a team leader stored in one table and the members of teams are stored in child table. I want to determine the teams that have same members.
TEAMS
TEAM_ID LEADER_ID
1 1
2 1
3 2
4 2
MEMBERS
TEAM_ID MEMBER_ID
1 2
1 3
1 4
2 3
2 4
2 5
3 1
3 3
3 4
4 5
4 6
4 7
I was able to write this query to determine the formations and now I am clueless how to proceed.
SELECT
TEAM_ID,
(
SELECT
CONVERT (VARCHAR, MEMBER_ID) + ', '
FROM
(
SELECT
TEAM_ID,
LEADER_ID AS MEMBER_ID
FROM
TEAMS
UNION ALL
SELECT
TEAM_ID,
MEMBER_ID
FROM
MEMBERS
) FORMATIONS
WHERE
TEAM_ID = MT.TEAM_ID
ORDER BY
MEMBER_ID FOR XML PATH ('')
) AS MEMBERS
FROM
TEAMS MT
As it is clear that team id 1 and 3 are same, how can the lowest ID of the duplicate teams can be obtained.
i.e. the query should return the list of TEAM_IDs that are smallest per duplicate group (and only if they are duplicate)
In this scenario id 1 should be returned.
http://sqlfiddle.com/#!18/c845a/5
There are worse ways to approach this than stuffing the members into a string and comparing them. So, I'll follow the route you have started.
All you need to do is to combine the members from the two tables, and then use that for the logic:
with m as (
select team_id, member_id
from members
union -- on purpose to remove duplicates
select team_id, leader_id
from teams
)
select *
from (select team_id, members, count(*) over (partition by members) as num_teams
from (select t.team_id,
stuff( (select concat(',', m.member_id)
from m
where m.team_id = t.team_id
order by m.member_id
for xml path ('')
), 1, 1, ''
) as members
from teams t
) t
) t
where num_teams > 1
order by members;
Here is your SQL Fiddle.
Note that string comparison works fine for this case, which is an exact match of members. For superset relationships is doesn't work so well.
Try below query, it uses CTE to get grouped teams wth its members listed as comma separated list.
Later it is used with group by to determine lowest ID of teams with same members. To ensure thhat there will be only duplicated teams, I used having clause.
;with cte as (
select team_id,
(select cast(member_id as varchar(5)) + ',' from #members innerMembers
where team_id = m.team_id
and not exists(select 1 from #TEAMS
where leader_id = innerMembers.member_id)
order by member_id
for xml path('')) members
from #members m
group by team_id
)
select min(team_id), members from cte
group by members
having count(*) > 1
Using pure SQL.
The main idea is that two sets A and B being equal is defined by A being a subset of B and B being a subset of A.
And we can check whether B is a subset of A by getting the members of B which are in A, counting them, and checking whether this equals the count in A.
As this is a somewhat complicated step I simply did it by cross applying a subquery filtered to teams A and B. There may be a more elegant way.
WITH MembersAll AS
(
SELECT Team_Id, Member_Id FROM Members
UNION
-- Consider leaders as members.
SELECT Team_Id, Leader_Id AS Member_Id FROM Teams
),
-- Teams and any teams which are a subset of that team:
TeamSubsetTeam AS (
SELECT
ThisTeam.Team_Id,
OtherTeam.Team_Id AS SubsetTeam_Id
FROM Teams AS ThisTeam
CROSS JOIN Teams AS OtherTeam -- Considering all pairs of teams.
CROSS APPLY (
-- Get the members in both teams,
-- left join so that we have all members from a given team
-- and all of the members in the other team that are in the given team
-- then filter on the counts of these being the same.
SELECT
COUNT(MembersThisTeam.Member_Id) AS MemberCountThisTeam,
COUNT(MembersOtherTeamInThisTeam.Member_Id) AS MemberCountOtherTeamInThisTeam
FROM MembersAll AS MembersThisTeam
LEFT JOIN MembersAll AS MembersOtherTeamInThisTeam
ON MembersThisTeam.Member_Id = MembersOtherTeamInThisTeam.Member_Id
AND MembersOtherTeamInThisTeam.Team_Id = OtherTeam.Team_Id
WHERE MembersThisTeam.Team_Id = ThisTeam.Team_Id
) MemberCounts
WHERE MemberCounts.MemberCountThisTeam = MemberCounts.MemberCountOtherTeamInThisTeam
),
-- Teams and any teams which are equivalent to that team (including itself):
TeamEquivalentTeam AS (
-- From set theory, team A is equivalent to team B if
-- team A is a subset of team B and
-- team B is a subset of team A.
SELECT
Team_Id,
SubsetTeam_Id AS EquivalentTeamId
FROM TeamSubsetTeam
WHERE Team_Id IN (
SELECT SubsetTeam_Id FROM TeamSubsetTeam AS SubsetTeamSubsetTeam
WHERE SubsetTeamSubsetTeam.Team_Id = TeamSubsetTeam.SubsetTeam_Id
)
)
-- The specified post-processing step.
-- Doesn't seem particularly useful but you can do whatever you like
-- now you have the information in TeamEquivalentTeam.
SELECT DISTINCT MIN(EquivalentTeamId) AS FirstEquivalentTeam
FROM TeamEquivalentTeam
GROUP BY Team_Id
Returns:
FirstEquivalentTeam
1
2
4

Find children of a most top level parent

There are similar question asking how to find the top level parent of a child (this, this and this). I have a similar question but I want to find all childern of a top level parent. This is similar question but uses wordpress predefined functions.
sample table:
id parent
1 0
2 0
3 1
4 2
5 3
6 3
7 4
I want to select ID with most top parent equals 1. The output should be 3 and all children of 3 I mean (5,6) and even more deep level children if available.
I know I can select them using two times of inner join but the hirearchy may be more complex with more levels.
A simple "Recursive CTE" will do what you want:
with n as (
select id from my_table where id = 1 -- starting row(s)
union all
select t.id
from n
join my_table t on t.parent_id = n.id
)
select id from n;
This CTE will go down all levels ad infinitum. Well... by default SQL Server limits it to 128 levels (that you can increase to 65k).
Since you aren't climbing the entire ladder...
select *
from YourTable
where parent = (select top 1 parent from YourTable group by parent order by count(parent) desc)
If you were wanting to return the parent of 3, since 3 was listed most often, then you'd use a recursive CTE.

sql select query self join or loop through to fetch records [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question appears to be off-topic because it lacks sufficient information to diagnose the problem. Describe your problem in more detail or include a minimal example in the question itself.
Closed 8 years ago.
Improve this question
I have this kind of scenario in sql server I have table named Room and here is the data of it and I want output something like this as shown in this picture I have tried to show my table named room and then on top of it I have placed tag input which have RoomId,ConnectingRoomID and many more other columns now what I want is a sql select query that can return me the scenario I have placed with tag name output..
These values are self created I have thousand of rooms and in room table and thousand of connecting room with it hope my question is clear enough thanks.
I think you can use this:
with x as (
select *, sum(case connectingroomid when 0 then 1 else 0 end) over(order by roomid) as grp
from rooms
)
select x.roomid, (select min(x2.roomid) as min_roomid from x x2 where x2.grp = x.grp) as connectingroomid
from x
This is a recursive query: For all rooms go to the connecting room till you find the one that has no more connecting room (i.e. connecting room id is 0).
with rooms (roomid, connectingroomid) as
(
select
roomid,
case when connectingroomid = 0 then
roomid
else
connectingroomid
end as connectingroomid
from room
where connectingroomid = 0
union all
select room.roomid, rooms.connectingroomid
from room
inner join rooms on room.connectingroomid = rooms.roomid
)
select * from rooms
order by connectingroomid, roomid;
Here is the SQL fiddle: http://www.sqlfiddle.com/#!3/46ed0/1.
EDIT: Here is the explanation. Rather than doing this in the comments I am doing it here for better readability.
The WITH clause is used to create a recursion here. You see I named it rooms and inside rooms I select from rooms itself. Here is how to read it: Start with the part before UNION ALL. Then recursively do the part after UNION ALL. So, before UNION ALL I only select the records where connectingroomid is zero. In your example you show every room with its connectingroomid except for those with connectingroomid for which you show the room with itself. I use CASE here to do the same. But now that I am explaining this, I notice that connectingroomid is always zero because of the WHERE clause. So the statement can be simplified thus:
with rooms (roomid, connectingroomid) as
(
select
roomid,
roomid as connectingroomid
from room where connectingroomid = 0
union all
select room.roomid, rooms.connectingroomid
from room
inner join rooms on room.connectingroomid = rooms.roomid
)
select * from rooms
order by connectingroomid, roomid;
The SQL fiddle: http://www.sqlfiddle.com/#!3/46ed0/2.
With the part before the UNION ALL I found the two rooms without connecting room. Now the part after UNION ALL is executed for the two rooms found. It selects the rooms which connecting room was just found. And then it selects the rooms which connecting room was just found. And so on till the join returns no more rooms.
Hope this helps understanding the query. You can look for "recursive cte" on the Internet to find more examples and explanations on the topic.
select RoomID,
(Case when RoomID<=157 then 154
else 158 end) ConnectingRoomID
from Input
First of all, your output is not correct: Room 154 should also connect to room 0 :-)
What you are after is the transitive closure of the relation defined by the table Room. It is impossible to get this with "vanilla" SQL. There are however, a few extensions to SQL to make recursive queries possible.
For example, If I have a relation "graph":
src | target
-----+--------
1 | 2
2 | 3
3 | 4
5 | 6
6 | 7
I can define a new table "closure" with the same fields:
WITH RECURSIVE closure (src, target) AS
(SELECT src, target FROM
graph
UNION
SELECT graph.src, closure.target FROM graph, closure WHERE
graph.target = closure.src)
SELECT * FROM closure
Note that "closure" occurs in its own definition (that is why this is a recursive query) It uses the original graph as a "seed" and grows by adding tuples with increasing distance (inspecting itself to do so).
The result (it clearly shows how the relation has grown):
src | target
-----+--------
1 | 2
2 | 3
3 | 4
5 | 6
6 | 7
1 | 3
2 | 4
5 | 7
1 | 4
If you are only interested in pairs that cannot be extended further, as in your original example, you could add an extra field "distance" to the closure table and use a GROUP BY clause to keep only the maximal pairs.
Disclaimer: I'm not on Windows, and used postgres for this. MS SQL should work very much the same way.
try below sql:
Assumming #input is your input table
Note: I added an ID column in the #input table
declare #input table
(
id int identity,
RoomId int,
ConnectingRoomId int
)
insert into #input
select 154,0 union all
select 155,154 union all
select 156,155 union all
select 157,156 union all
select 158, 0 union all
select 159, 158 union all
select 160, 159
**UPDATED: remove the union **
SQL:
select
d.id,
d.roomId
,max(d.connectingRoomId) as ConnectingRoomId
from
(
select
bb.id,
bb.RoomId
,b.RoomId as connectingRoomId
from #input b
right join
(
select
a.id,
a.RoomId,a.ConnectingRoomId
from #input a
) bb on (b.id < bb.Id) or b.Id = bb.Id
where b.ConnectingRoomId = 0
) d
group by d.id, d.RoomId
/*
Result (OUTPUT TABLE)
id roomId ConnectingRoomId
----------- ----------- ----------------
1 154 154
2 155 154
3 156 154
4 157 154
5 158 158
6 159 158
7 160 158
*/

T-SQL Select, ensure manager row comes before employee

Here is my problem. I have a table of employee data, which contains the username of the employee and their manager's username like so
______________________________________________
| employee_username | employee_manager_username|
------------------------------------------------
tom01 | mark2
mark2 | bill3
My question is, how can I do a select, where for any row, the row containing the employee_manager_username as the employee_username comes before ? I guess using my example, how do I make it so that in a select, the [mark3,bill3] row comes before the [tom01,mark2] row.
Long story short, I have a 3rd party import process that warns about the manager not being found. Being able to have the manager imported before their subordinates would make the logs a little less chatty.
Any help on this is greatly appreciated.
I supposing that you have top level managers have employee_manager_username = null. In that case you could to recursive Common table Expression and then order by level:
;with CTE as (
select t.employee_username, t.employee_manager_username, 1 as level
from table1 as t
where
t.employee_manager_username is null or
t.employee_manager_username = t.employee_username
union all
select t.employee_username, t.employee_manager_username, c.level + 1 as level
from table1 as t
inner join CTE as c on c.employee_username = t.employee_manager_username
where
not (
t.employee_manager_username is null or
t.employee_manager_username = t.employee_username
)
)
select *
from CTE
order by level asc
SQL FIDDLE EXAMPLE to test query