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.
Related
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.
Starting from the generic node (or leaf) how to get the root node?
SELECT
ID,MSG_ID,PARENT_ID
FROM
TABLE_X
CONNECT BY PRIOR PARENT_ID = ID;
ID MSG_ID PARENT_ID
4 3 NULL
5 93bea0f71b07-4037-9009-f148fa39bb62 4
4 3 NULL
6 6f5f5d4ab1ec-4f00-8448-7a6dfa6461b2 4
4 3 NULL
7 3 NULL
8 7e0fae569637-4d29-9075-c273eb39ae8e 7
7 3 NULL
9 8a3e7485b3e8-45b1-a31d-c52fd32111c0 7
7 3 NULL
10 fcc622d5af92-4e61-8d7c-add3da359a8b 7
7 3 NULL
How to get the root msg_id?
You were on the right path, but you are missing two essential ingredients.
First, to indicate the starting point, you need to use the start with clause. Obviously, it will be something like start with id = <input value>. You didn't tell us how you will provide the input value. The most common approach is to use bind variables (best for many reasons); I named the bind variable input_id in the query below.
Second, you only want the "last" row (since you are navigating the tree in the opposite direction: towards the root, not from the root; so the root is now the "leaf" as you navigate this way). For that you can use the connect_by_isleaf pseudocolumn in the where clause.
So, the query should look like this: (note that I am only selecting the root message id, as that is all you requested; if you need more columns, include them in select)
select msg_id
from table_x
where connect_by_isleaf = 1 -- keep just the root row (leaf in this traversal)
start with id = :input_id -- to give the starting node
connect by prior parent_id = id
;
You can use a recursive query:
with cte (id, msg_id, parent_id) as (
select id, msg_id, parent_id, from mytable where id = ?
union all
select t.id, t.msg_id, t.parent_id
from mytable t
inner join cte c on c.parent_id = t.id
)
select * from cte where parent_id is null
I have table and I want get 3 lvl tree.
Example
Node
Id ParentId Name
1 -1 Test 1
2 -1 Test 2
3 1 Test 1.1
4 1 Test 1.2
5 3 Test 1.1.1
6 3 Test 1.1.2
7 5 Test 1.1.1.1
If I filtered ParentId = -1 I want get rows ParentId = -1 and children's +2
lvl.
If I filtered Id = 2 I want get row Id = 2 and children's +2
lvl.
UPDATE
I use MS SQL Server 2008, Entity Framework 6.1.3.
I understand, I can use 3 selects. But I looking effective method
You can use recursive SQL to do this in SQL server.
WITH recCTE (childID, parentID, Name, Depth) Assuming
(
Select
yourTable.id as childid,
yourTable.parentID,
CAST('Test ' + yourTable.id as varchar(20)) as Name
0 as Depth
FROM
yourTable
WHERE
parentID = -1
UNION ALL
Select
yourTable.id as childID,
yourTable.ParentID as ParentID,
recCTE.path + '.' + yourTable.id AS Name
recCTE.depth + 1 as Depth
FROM
recCTE
INNER JOIN yourTable on
recCTE.childID = yourTable.parentID
Where
recCTE.Depth + 1 <= 2
)
SELECT * FROM recCTE;
The bit inside the CTE on top of the UNION is your seed query for the recursive sql. It's the place where your recursive lookup will start. You wanted to start at parentID = -1, so it's here in the WHERE statement.
The bit inside the CTE below the UNION is the recursive term. This joins the recursive CTE back to itself and brings in more data from your table. Joining the id from your table to the childID from the recursive resultset.
The recursive term is where we test to see how deep we've gotten. If the Depth from the CTE + 1 is less than or equal to 2 then we stop adding children to the loop up, ending the loop for that particular leg of the hierarchy.
The last little bit below the CTE is just the part that runs the CTE so you get results back.
These recursive queries are confusing as hell at first, but spend some time with them and you'll find a lot of use for them. You'll also find that they aren't too difficult to write once you have all the parts sussed out.
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.
I am using SQL Server 2008. I have a table like this:
UnitId ParentId UnitName
---------------------------
1 0 FirstUnit
2 1 SecondUnit One
3 1 SecondUnit Two
4 3 B
5 2 C
6 4 D
7 6 E
8 5 F
I want to get second parent of the record. For example:
If I choose unit id that equal to 8, It will bring unit id is equal to 2 to me. It needs to be SecondUnit One. or If I choose unit id that equal to 7, It will bring unit id is equal to 3 to me. It needs to be SecondUnit Two.
How can I write a SQL query this way?
It took me a while, but here it is :)
with tmp as (
select unitId, parentId, unitName, 0 as iteration
from t
where unitId = 7
union all
select parent.unitId, parent.parentId, parent.unitName, child.iteration + 1
from tmp child
join t parent on child.parentId = parent.unitId
where parent.parentId != 0
)
select top 1 unitId, parentId, unitName from tmp
order by iteration desc
Here is also a fiddle to play with.
SELECT t.*, tParent1.UnitId [FirstParent], tParent2.UnitId [SecondParent]
FROM Table t
LEFT JOIN Table tParent1 ON t.ParentId = tParent1.UnitId
LEFT JOIN Table tParent2 ON tParent1.ParentId = tParent2.UnitId
WHERE t.UnitId = <Unit ID search here>
AND NOT tParent2.UnitId IS NULL
Edit: And leave out second part of the WHERE clause if you want results returned even if they don't have a second parent.