I got a very simple table, but I'm struggling with CTE:
ID | Parent | Name
---+--------+---------
1 | NULL | Root
2 | 1 | Child1
3 | 2 | Child2
I'd like to receive a result like this:
Element | Root
--------+------
Root | Root
Child1 | Root
Child2 | Root
My CTE is something like this...
WITH cte AS
(
SELECT a.id, a.parent, a.name, 1 as lvl
FROM table1 a
UNION ALL
-- Perform the recursive join
SELECT a.id, a.parent, a.name, Lvl+1 AS Lvl
FROM table1 a
INNER JOIN cte pa ON cte.parent = a.id
)
SELECT *
FROM cte
Now I would aggregate (max) and (self)join but it seems kinda bad/wrong.
This is a little complicated, because you are traversing ids but in the end, you just want names. The following handles this by looking up the names after the recursive CTE has found the root ids:
with cte as (
select id, parent, name, 1 as lev
from t
union all
select cte.id, t.parent, cte.name, lev + 1
from cte join
t
on t.id = cte.parent
where t.parent is not null
)
select top (1) with ties cte.name, coalesce(tparent.name, cte.name) as root
from cte left join
t tparent
on tparent.id = cte.parent
order by row_number() over (partition by cte.id order by cte.lev desc)
Here is a db<>fiddle.
Try
with cte as (
select id, parent, name, name as root
from t
where parent is null
union all
select t.id, t.parent, t.name, cte.root
from cte
join t on t.parent = cte.id
)
select name, root
from cte
Related
My table is as follows:
ID Name Parent ID
1 Joe -
2 James -
3 Mike 1
4 Lewis 3
5 Anne 2
6 Lucy 4
I'd like to get the ID of the parent and all its children. For example, if I do:
Select Name from Table where ID = 1 (and nested children)
The desired output would be:
Joe
Mike
Lewis
Lucy
You can use a recursive CTE:
with cte as (
select name, id
from t
where id = 1
union all
select t.name, t.id
from cte join
t
on t.parent_id = cte.id
)
select name
from cte;
WITH CTE_Table AS (
SELECT ID, Name, Parent
FROM MyTable
UNION ALL
SELECT MyTable.ID, MyTable.Name, MyTable.Parent
FROM CTE_Table INNER JOIN
MyTable ON CTE_Table.ID = MyTable.Parent
)
SELECT Name
FROM CTE_Table
WHERE (Parent = 1)
I have a table for ledgers. It consists of MasterID as unique ID and a parentID for joining.
Model of my table
As in the image my parent ledger is 'A'. 'B' is direct child of 'A'. 'C' is direct child of 'B' and 'D' is direct child of 'C'.
I want a select query to select all childs of 'A'. i.e, result will be B,C,D.
I am beginner in SQL.
I tried some while loop for this, but only direct child was accessible. I am not able make a logic for the requirement.
Thank you in advance.
That'as a typical hierarchical query. Consider the solution that makes use of a recursive cte:
with cte as (
select masterID rootID, masterID, name, parentid
from mytable
where parentID is null
union all
select c.rootID, t.masterID, t.name, t.parentID
from mytable t
inner join cte c on c.masterID = t.parentID
)
select * from cte
As commented by The Impaler, you can change starting condition where parentID is null to the id of another node if needed.
With your sample data, this yields:
rootID | masterID | name | parentid
-----: | -------: | :--- | -------:
1 | 1 | A | null
1 | 2 | B | 1
1 | 3 | C | 2
1 | 4 | D | 3
Note that I kept track of the id of the root object, so it is easier to understand what is going on if there are several roots in your data.
You can also use the root to generate a flat list of children:
with cte as (
select masterID rootID, masterID, name, parentid
from mytable
where parentID is null
union all
select c.rootID, t.masterID, t.name, t.parentID
from mytable t
inner join cte c on c.masterID = t.parentID
)
select rootID, string_agg(masterID, ',') childrenID from cte group by rootID
rootID | childrenID
-----: | :---------
1 | 1,2,3,4
Demo on DB Fiddle
Here is an option that uses HierarchyID
The range keys (R1 / R2) in the final select are optional.
Example
Declare #YourTable table (MasterID int ,name varchar(50), ParentID int);
Insert Into #YourTable values
( 1, 'A', null),
( 2, 'B', 1),
( 3, 'C', 2),
( 4, 'D', 3)
Declare #Top int = 2 --<< Sets top of Hier Try NULL for entire hier
Declare #Nest varchar(25) = '|-----' --<< Optional: Added for readability
;with cteP as (
Select MasterID
,ParentID
,Name
,HierID = convert(hierarchyid,concat('/',MasterID,'/'))
From #YourTable
Where IsNull(#Top,-1) = case when #Top is null then isnull(ParentID ,-1) else MasterID end
Union All
Select MasterID = r.MasterID
,ParentID = r.ParentID
,Name = r.Name
,HierID = convert(hierarchyid,concat(p.HierID.ToString(),r.MasterID,'/'))
From #YourTable r
Join cteP p on r.ParentID = p.MasterID)
Select R1 = Row_Number() over (Order By HierID)
,R2 = (Select count(*) From cteP where HierID.ToString() like A.HierID.ToString()+'%')
,Lvl = HierID.GetLevel()
,MasterID
,ParentID
,Name = Replicate(#Nest,HierID.GetLevel()-1) + Name
,HierID
,HierID_String = HierID.ToString()
From cteP A
Order By A.HierID
Returns
I want a count of all child and sub child of where a particular parent exists.
I have this data:
| Id | Parent
+-----------------------------------------+-------------------------------------+
| 736F8C6A-D58D-442E-BE2E-3B9F0595C58B | NULL |
| CA828BBA-6657-46FC-BA26-7ED8C7FB220C | 736F8C6A-D58D-442E-BE2E-3B9F0595C58B|
| 2DB8A8F9-9F29-4F3A-907F-A6ACEDE12859 | 736F8C6A-D58D-442E-BE2E-3B9F0595C58B|
If I pass 736F8C6A-D58D-442E-BE2E-3B9F0595C58B, then it should return the total count of its children and grandchildren - that is a total of 2.
Any suggestions?
First, i used the following query to create the temp table
CREATE TABLE #TBLTEMP(ID int, ParentID int)
INSERT INTO #TBLTEMP(ID,ParentID)
VALUES(1,NULL)
,(2,1)
,(3,2)
,(4,2)
,(5,3)
,(6,NULL)
,(7,6)
,(8,NULL)
,(9,NULL)
Then i used the following query to retrieve childs count
WITH CTE_1(ParentID) AS(
SELECT ParentID FROM #TBLTEMP T1
UNION ALL
SELECT T2.ParentID FROM CTE_1 c
INNER JOIN #TBLTEMP T2 ON c.ParentID = T2.ID
WHERE T2.ParentID > 0
), CTE_2 AS ( SELECT ID , 0 as cnt FROM #TBLTEMP T3 WHERE NOT EXISTS (SELECT 1 FROM CTE_1 c2 where c2.ParentID = T3.ID))
SELECT ParentID, count(*) as cnt
FROM CTE_1
WHERE ParentID IS NOT NULL
GROUP BY ParentID
UNION
SELECT ID as ParentID, cnt FROM CTE_2
ORDER BY ParentID
The result is:
Reference
Recursive CTE to find Total for all children
Update 1
To getcount for a specific ID:
WITH CTE_1(ParentID) AS(
SELECT ParentID FROM #TBLTEMP T1
UNION ALL
SELECT T2.ParentID FROM CTE_1 c
INNER JOIN #TBLTEMP T2 ON c.ParentID = T2.ID
WHERE T2.ParentID > 0
), CTE_2 AS ( SELECT ID , 0 as cnt FROM #TBLTEMP T3 WHERE NOT EXISTS (SELECT 1 FROM CTE_1 c2 where c2.ParentID = T3.ID)), CTE_3 AS(
SELECT ParentID, count(*) as cnt
FROM CTE_1
WHERE ParentID IS NOT NULL
GROUP BY ParentID
UNION
SELECT ID as ParentID, cnt FROM CTE_2)
SELECT cnt FROM CTE_3 WHERE ParentID = 1
In SQL Server, I have a table with sample data as shown below:
SELECT parent_id,child_id FROM tab a
parent_id child_id
--------------------
1 2
2 3
3 4
1 5
5 6
12 13
And I want to fetch data with parent_id for all children like this:
Parent_id Child_id
--------------------
1 2
1 3
1 4
1 5
1 6
12 13
Where first parent is shown for all relevant children. I have tried below,
WITH cte_Recursive( Parent, Child, Level ) AS
(
SELECT T.Parent_id, T.Child_id, 1 AS Level
FROM tab AS T
WHERE NOT EXISTS(SELECT * FROM tab AS TI
WHERE T.Child_id = TI.Parent_id)
UNION ALL
SELECT TR.Parent_id, TR.Child_id, Level + 1
FROM tab AS TR
INNER JOIN cte_Recursive CR ON TR.Child_id = CR.Parent
)
SELECT
Parent, Child
FROM
(SELECT
CR.Parent, CR.Level, iTVF.Child,
max_level = MAX(CR.Level) OVER (PARTITION BY NULL)
FROM
cte_Recursive CR
CROSS APPLY
(SELECT
CRI.Parent, CRI.Child
FROM
cte_Recursive CRI
WHERE
CR.Level >= CRI.Level) iTVF
) AS S
WHERE
Level = max_level
But it does not show the expected result
A query like below will help
See working demo here
; with cte as
(
select t1.parent_id, t1.child_id
from tab t1
left join tab t2 on t1.parent_id = t2.child_id
where t2.parent_id is null
union all
select c.parent_id, t2.child_id
from cte c
join tab t2 on t2.parent_id = c.child_id
)
select *
from cte
order by child_id
Let's say I have a database schema like this:
RowId ParentId Name
------ ---------- ------
1 NULL Level1
2 NULL Level2
3 1 Leaf1
4 1 Leaf2
5 2 Leaf1
6 3 LeafX
Basically, the tree would look as such:
Level1
Leaf1
LeafX
Leaf2
Level2
Leaf1
I need to extract all ancestor LEVEL of LeafX in the most efficient and dynamic way.
So it will output: Leaf1, Leaf2, and Leaf1 (of Level2)
How do I do this in T-SQL? Thanks
This will give you the result you want.
;with C as
(
select T.rowid,
T.parentid,
T.name,
1 as Lvl
from YourTable as T
where T.parentid is null
union all
select T.rowid,
T.parentid,
T.name,
C.Lvl + 1
from YourTable as T
inner join C
on T.parentid = C.rowid
)
select *
from C
where C.Lvl = (
select C.lvl-1
from C
where C.name = 'LeafX'
)
Update
And this might be faster for you. You have to test on your data.
declare #Level int;
with C as
(
select T.rowid,
T.parentid
from #t as T
where T.name = 'LeafX'
union all
select T.rowid,
T.parentid
from #t as T
inner join C
on T.rowid = C.parentid
)
select #Level = count(*) - 1
from C;
with C as
(
select T.rowid,
T.parentid,
T.name,
1 as Lvl
from #t as T
where T.parentid is null
union all
select T.rowid,
T.parentid,
T.name,
C.Lvl + 1
from #t as T
inner join C
on T.parentid = C.rowid
where C.Lvl < #Level
)
select *
from C
where C.Lvl = #Level;
There's a few methods to do that. My favourite is to create special table Trees_Parents, where you will store every parent for evere node.
So if have structure like that
RowId ParentId Name
------ ---------- ------
1 NULL Level1
2 NULL Level2
3 1 Leaf1
4 1 Leaf2
5 2 Leaf1
6 3 LeafX
your Trees_Parents table will looks like
RowId ParentId
------ ----------
1 1
2 2
3 3
3 1
4 4
4 1
5 5
5 2
6 6
6 1
6 3
then when you need to retrieve all children you just write
select RowID from Trees_Parents where ParentId = 1
I'm storing row self in this table to avoid unions, if you don't need it you can write
select RowID from Trees_Parents where ParentId = 1 and ParentId <> RowId
And for all parents you'll write
select ParentId from Trees_Parents where RowId = 6 and ParentId <> RowId
You can also store Table_Name in table Trees_Parents so you can use it for different tables
Another way is to write recursive WITH clause, but if your tree is big and it's not changing frequently I think it's better to store parents data in additional table
Well you can use recursive solution. You need to get all nodes with Depth = Depth of your node - 1
declare #Temp table (RowId int, ParentId int, Name nvarchar(128))
insert into #Temp
select 1, null, 'Level1' union all
select 2, null, 'Level2' union all
select 3, 1, 'Leaf1' union all
select 4, 1, 'Leaf2' union all
select 5, 2, 'Leaf3' union all
select 6, 3, 'LeafX';
with Parents
as
(
select T.RowId, 0 as Depth from #Temp as T where T.ParentId is null
union all
select T.RowId, P.Depth + 1
from Parents as P
inner join #Temp as T on T.ParentId = P.RowId
)
select T.Name
from Parents as P
outer apply (select TT.Depth from Parents as TT where TT.RowId = 6) as CALC
left outer join #Temp as T on T.RowId = P.RowId
where P.Depth = CALC.Depth - 1
declare #t table(rowid int, parentid int, name varchar(10))
insert #t values(1,NULL,'Level1')
insert #t values(2,NULL,'Level2')
insert #t values(3,1,'Leaf1')
insert #t values(4,1,'Leaf2')
insert #t values(5,2,'Leaf1')
insert #t values(6,3,'LeafX')
;with a as
(
select rowid, parentid, 0 level from #t where name = 'leafx'
union all
select t.rowid, t.parentid, level + 1 from #t t
join a on a.parentid = t.rowid
), b as
(
select rowid, parentid,name, 0 level from #t where parentid is null
union all
select t.rowid, t.parentid,t.name, level + 1
from b join #t t on b.rowid = t.parentid
)
select rowid, parentid, name from b
where level = (select max(level)-1 from a)
rowid parentid name
5 2 Leaf1
3 1 Leaf1
4 1 Leaf2