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
Related
I am trying to build a deep recursive self-join query. Having the table like:
Id | ParentId
1 | NULL
2 | 1
3 | 1
4 | 2
5 | 3
6 | 8
7 | 9
For Id 1 my query should be fetching 1,2,3,4,5 since they are either the children of 1 or children of the children of 1. In the given example 6 and 7 should not be included in the query result.
I tried using CTE but I am getting tons of duplicates:
WITH CTE AS (
SELECT Id, ParentId
FROM dbo.Table
WHERE ParentId IS NULL
UNION ALL
SELECT t.Id, t.ParentId
FROM dbo.Table t
INNER JOIN CTE c ON t.ParentId = c.Id
)
SELECT * FROM CTE
Ideas?
You can try to use DISTINCT to filter duplicate rows.
;WITH CTE AS (
SELECT Id, ParentId
FROM T
WHERE ParentId IS NULL
UNION ALL
SELECT t.Id, t.ParentId
FROM T
INNER JOIN CTE c ON t.ParentId = c.Id
)
SELECT DISTINCT Id, ParentId
FROM CTE
Try the following query with CTE where you can set parentId by #parentID:
DECLARE #parentID INT = 1
;WITH cte AS
(
SELECT
t.ID
, t.ParentId
FROM #table t
),
cteParent AS
(
SELECT
t.ID
, t.ParentId
FROM #table t
WHERE t.ParentId IN (SELECT t1.ID FROM #table t1 WHERE T1.ParentId = #parentID)
)
SELECT
DISTINCT c1.ID
, c1.ParentId
FROM cte c1
INNER JOIN cte c2 ON c2.ParentId = c1.ID
UNION ALL
SELECT *
FROM cteParent
And the sample data:
DECLARE #table TABLE
(
ID INT
, ParentId INT
)
INSERT INTO #table
(
ID,
ParentId
)
VALUES
(1, NULL )
, (2, 1 )
, (3, 1 )
, (4, 2 )
, (5, 3 )
, (6, 8 )
, (7, 9 )
OUTPUT:
ID ParentId
1 NULL
2 1
3 1
4 2
5 3
I don't see duplicates.
Your code returns the following on the data you provided:
Id ParentId
1
2 1
3 1
5 3
4 2
which is what you want.
Here is a db<>fiddle.
Here is the code:
WITH t as (
SELECT *
FROM (VALUES (1, NULL), (2, 1), (3, 1), (4, 2), (5, 3), (6, 8), (7, 9)
) v(id, parentId)
),
CTE AS (
SELECT Id, ParentId
FROM t
WHERE ParentId IS NULL
UNION ALL
SELECT t.Id, t.ParentId
FROM t
INNER JOIN CTE c ON t.ParentId = c.Id
)
SELECT *
FROM CTE;
If you are getting duplicates in your actual result set, then you presumably have duplicates in your original table. I would recommend removing them before doing the recursive logic:
with t as (
select distinct id, parentid
from <your query>
),
. . .
Then run the recursive logic.
Try this sql script which result Parent Child Hierarchy
;WITH CTE(Id , ParentId)
AS
(
SELECT 1 , NULL UNION ALL
SELECT 2 , 1 UNION ALL
SELECT 3 , 1 UNION ALL
SELECT 4 , 2 UNION ALL
SELECT 5 , 3 UNION ALL
SELECT 6 , 8 UNION ALL
SELECT 7 , 9
)
,Cte2
AS
(
SELECT Id ,
ParentId ,
CAST('\'+ CAST(Id AS VARCHAR(MAX))AS VARCHAR(MAX)) AS [Hierarchy]
FROM CTE
WHERE ParentId IS NULL
UNION ALL
SELECT c1.Id ,
c1.ParentId ,
[Hierarchy]+'\'+ CAST(c1.Id AS VARCHAR(MAX)) AS [Hierarchy]
FROM Cte2 c2
INNER JOIN CTE c1
ON c1.ParentId = c2.Id
)
SELECT Id,
RIGHT([Hierarchy],LEN([Hierarchy])-1) AS ParentChildHierarchy
FROM Cte2
GO
Result
Id ParentChildHierarchy
-------------------------
1 1
2 1\2
3 1\3
5 1\3\5
4 1\2\4
This query will help you
CREATE TABLE #table( ID INT, ParentId INT )
INSERT INTO #table(ID,ParentId)
VALUES (1, NULL ), (2, 1 ), (3, 1 ), (4, 2 ), (5, 3 ), (6, 8 ), (7, 9 )
;WITH CTE AS (
SELECT ID FROM #table WHERE PARENTID IS NULL
UNION ALL
SELECT T.ID FROM #table T
INNER JOIN #table T1 ON T.PARENTID =T1.ID
) SELECT * FROM CTE
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
I have three excel sheet I push them into tables in SQL server and I need to join these table. However, I believe - as I have tried already - normal join wouldn't work. I have programming background but not that much with SQL.
Table1
ID Data_column reference_number
1 some data 1528,ss-456
2 some data 9523
3 some data ss-952
4 some data null
Table2
ID Data_column
ss-456 some data
ss-952 some data
Table3
ID Data_column
1528 some data
9523 some data
In the case below How I will be able to join this raw on both table.
Table1
ID Data_column reference_number
1 some data 1528,ss-456
declare #t1 as table(
id int
,data_column varchar(20)
,reference_number varchar(20)
)
declare #t2 as table(
id varchar(20)
,data_column varchar(20)
)
declare #t3 as table(
id varchar(20)
,data_column varchar(20)
)
insert into #t1 values(1,'some data','1528,ss-456'),(2,'some data','9523'),(3,'some data','ss-952'),(4,'some data',null);
insert into #t2 values('ss-456','some data'),('ss-952','some data');
insert into #t3 values(1528,'some data'),(9523,'some data');
Quick solution
select * from #t1 t1
left outer join #t2 t2 on t1.reference_number like '%'+t2.id or t1.reference_number like t2.id+'%'
left outer join #t3 t3 on t1.reference_number like '%'+t3.id or t1.reference_number like t3.id+'%'
Result (left join):
id data_column reference_number id data_column id data_column
1 some data 1528,ss-456 ss-456 some data 1528 some data
2 some data 9523 NULL NULL 9523 some data
3 some data ss-952 ss-952 some data NULL NULL
4 some data NULL NULL NULL NULL NULL
You can change 'left outer join' to 'inner join' for exact match.
Clumsy design, clumsy solution:
SELECT *
FROM Table1
INNER JOIN Table2 ON ',' + Table1.reference_number + ',' LIKE '%,' + Table2.ID + ',%'
INNER JOIN Table3 ON ',' + Table1.reference_number + ',' LIKE '%,' + Table3.ID + ',%'
You must append leading and trailing commas to make sure that, for example, 1528,ss-456asdf does not match %ss-456%.
I see two problems here. First is the inconsistent type of ID in table 2 and 3 and aggregation of referenced keys in table 1. Here is an example how to solve both problems. To split REFERENCE_NUMBER column I used STRING_SPLIT function.
Update:
I added the solution which should work with SQL Server 2012.
I assumed that you wish to join data from table 1 with 2 or 3 depending in existence of this data. This is just my idea what you wanted to achive.
-- data preparing
declare #t1 as table(
id int
,data_column varchar(20)
,reference_number varchar(20)
)
declare #t2 as table(
id varchar(20)
,data_column varchar(20)
)
declare #t3 as table(
id int
,data_column varchar(20)
)
insert into #t1 values(1,'some data','1528,ss-456'),(2,'some data','9523'),(3,'some data','ss-952'),(4,'some data',null);
insert into #t2 values('ss-456','some data'),('ss-952','some data');
insert into #t3 values(1528,'some data'),(9523,'some data');
-- Solution example version >= 2016
with base as (
select t1.id,t1.data_column,f1.value from #t1 t1 outer apply string_split(t1.reference_number,',') f1)
select b.id,b.data_column,b.value,t2.data_column from base b join #t2 t2 on b.value = t2.id
union all
select b.id,b.data_column,b.value,t3.data_column from base b join #t3 t3 on try_cast(b.value as int ) = t3.id
union all
select b.id,b.data_column,b.value,null from base b where b.value is null;
-- Solution for SQL Version < 2016
with base as (
select t1.id,t1.data_column,f1.value from #t1 t1 outer apply(
SELECT Split.a.value('.', 'NVARCHAR(MAX)') value
FROM
(
SELECT CAST('<X>'+REPLACE(t1.reference_number, ',', '</X><X>')+'</X>' AS XML) AS String
) AS A
CROSS APPLY String.nodes('/X') AS Split(a)
) f1)
select b.id,b.data_column,b.value,t2.data_column from base b join #t2 t2 on b.value = t2.id
union all
select b.id,b.data_column,b.value,t3.data_column from base b join #t3 t3 on try_cast(b.value as int ) = t3.id
union all
select b.id,b.data_column,b.value,null from base b where b.value is null;
You will require a function to divide the comma separated sting into rows. If you don't have access to thr inbuilt string_split() function (as of mssql 2017 with compatibility of 130) there are several to choose from here
CREATE TABLE table1(
ID INTEGER NOT NULL PRIMARY KEY
,Data_column VARCHAR(10) NOT NULL
,reference_number VARCHAR(11)
);
INSERT INTO table1(ID,Data_column,reference_number) VALUES
(1,'t1somedata','1528,ss-456')
, (2,'t1somedata','9523')
, (3,'t1somedata','ss-952')
, (4,'t1somedata',NULL);
CREATE TABLE table2(
ID VARCHAR(6) NOT NULL PRIMARY KEY
,Data_column VARCHAR(10) NOT NULL
);
INSERT INTO table2(ID,Data_column) VALUES
('ss-456','t2somedata'),
('ss-952','t2somedata');
CREATE TABLE table3(
ID VARCHAR(6) NOT NULL PRIMARY KEY
,Data_column VARCHAR(10) NOT NULL
);
INSERT INTO table3(ID,Data_column) VALUES
('1528','t3somedata'),
('9523','t3somedata');
I have used this splistring function, but you can use almost any of the many freely available.
CREATE FUNCTION dbo.SplitStrings_Moden
(
#List NVARCHAR(MAX),
#Delimiter NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING AS
RETURN
WITH E1(N) AS ( SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1),
E2(N) AS (SELECT 1 FROM E1 a, E1 b),
E4(N) AS (SELECT 1 FROM E2 a, E2 b),
E42(N) AS (SELECT 1 FROM E4 a, E2 b),
cteTally(N) AS (SELECT 0 UNION ALL SELECT TOP (DATALENGTH(ISNULL(#List,1)))
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E42),
cteStart(N1) AS (SELECT t.N+1 FROM cteTally t
WHERE (SUBSTRING(#List,t.N,1) = #Delimiter OR t.N = 0))
SELECT Item = SUBSTRING(#List, s.N1, ISNULL(NULLIF(CHARINDEX(#Delimiter,#List,s.N1),0)-s.N1,8000))
FROM cteStart s;
This is what the data looks like using the splitstring function:
select *
from table1
cross apply SplitStrings_Moden(reference_number,',')
ID | Data_column | reference_number | Item
-: | :---------- | :--------------- | :-----
1 | t1somedata | 1528,ss-456 | 1528
1 | t1somedata | 1528,ss-456 | ss-456
2 | t1somedata | 9523 | 9523
3 | t1somedata | ss-952 | ss-952
4 | t1somedata | null | null
and now joining to the other tables:
select
*
from (
select *
from table1
cross apply SplitStrings_Moden(reference_number,',')
) t1
left join table2 on t1.item = table2.id
left join table3 on t1.item = table3.id
where t1.item is not null
GO
ID | Data_column | reference_number | Item | ID | Data_column | ID | Data_column
-: | :---------- | :--------------- | :----- | :----- | :---------- | :--- | :----------
1 | t1somedata | 1528,ss-456 | 1528 | null | null | 1528 | t3somedata
1 | t1somedata | 1528,ss-456 | ss-456 | ss-456 | t2somedata | null | null
2 | t1somedata | 9523 | 9523 | null | null | 9523 | t3somedata
3 | t1somedata | ss-952 | ss-952 | ss-952 | t2somedata | null | null
db<>fiddle here
TRY THIS: If your reference_number is fixed and always stored IDs upto 2 only then you can go with the below approach
SELECT *
FROM(
SELECT ID,
data_column,
CASE WHEN PATINDEX ( '%,%', reference_number) > 0 THEN
SUBSTRING(reference_number, PATINDEX ( '%,%', reference_number)+1, LEN(reference_number))
ELSE reference_number END AS ref_col
FROM #table1
UNION
SELECT ID,
data_column,
CASE WHEN PATINDEX ( '%,%', reference_number) > 0 THEN
SUBSTRING(reference_number, 0, PATINDEX ( '%,%', reference_number))
END
FROM #table1) t1
LEFT JOIN #table2 t2 ON t2.id = t1.ref_col
LEFT JOIN #table3 t3 ON t3.id = t1.ref_col
WHERE t1.ref_col IS NOT NULL
OUTPUT:
ID data_column ref_col ID Data_column ID Data_column
1 some data 1528 NULL NULL 1528 some data
1 some data ss-456 ss-456 some data NULL NULL
2 some data 9523 NULL NULL 9523 some data
3 some data ss-952 ss-952 some data NULL NULL
4 some data null NULL NULL NULL NULL
You can Implement and get desired result using Substring and charIndex functions on the reference_number.
I upvoted an answer of 'is_oz' since i used his ready made schema to test and build a query for you.
below is the final query i build after several tries i made here:
select * from abc
left join abc2 on abc2.id = case when charindex(',',abc.reference_number) > 0
then substring(abc.reference_number
,charindex(',',abc.reference_number)+1
,len(abc.reference_number)-(charindex(',',abc.reference_number)-1)
)
else abc.reference_number
end
left join abc3 on abc3.id = case when charindex(',',abc.reference_number) > 0
then substring(abc.reference_number
,0
,(charindex(',',abc.reference_number))
)
else abc.reference_number
end
As per your requirement as much as i understand, it is returning all the matched rows from 2 other tables but still i hope this fulfills all the requirements you seek in your question. :)
I have a table in this form:
id | firstname | lastname | userid
---+-----------+------------------------
1 | john | smith | 545868-5434-343435-35353
2 | adam | finger | 545868-5434-343435-35353
3 | teri | marti | 545868-5434-343435-35353
4 | pei | port | 545868-5434-343435-35353
In the DB i have many userid i need to populate the very same firstname and lastname to all userid found in the Database
Here is my SQl Query
SELECT
cID, c.firstname,c.lastname,
[s].UserID,c.OwnerID
FROM
Customer INNER JOIN [s] ON c.OwnerID = [s].UserID AND c.AssignedtoID =
[s].UserID AND c.CreatedByUserID = [s].UserID
AssignedtoID is the same as UserID
is this helpful for you.?
Create table #tmpCustomer (id int, firstname VARCHAR(50),lastname VARCHAR(50),userid VARCHAR(100))
INSERT INTO #tmpCustomer
SELECT 1, 'john','smith','545868-5434-343435-35353'
union
SELECT 2,'adam','finger','545868-5434-343435-35353'
union
SELECT 3,'teri','marti','545868-5434-343435-35353'
union
SELECT 4, 'pei','port','545868-5434-343435-35353'
union
SELECT 5, 'abc','xyz','545868-5434-343435-35354'
union
SELECT 6, 'mno','ert','545868-5434-343435-35354'
--select * from #tmpCustomer
;with cte1 AS(Select row_number()over(partition by userid order by id) rn,* from #tmpCustomer ),
cte2 AS (select * from cte1 where rn=1 )
update t
set t.firstname=c.firstname
from #tmpCustomer t
JOIN cte2 c on t.userid=c.userid
select * from #tmpCustomer
drop table #tmpCustomer
i don't know if i good understand your question, try solution posted below
DECLARE #cust as table (firstname varchar(20),lastname Varchar(20))
Insert #cust
values
('Suzan','Smith')
declare #id as table (id int identity,anything varchar(20),row_inserted datetime2 default (cast(sysdatetime() as datetime2)))
INSERT #id
(anything,row_inserted)
SELECT 'x' ,'20180305'
union all
select 'y','20180305'
union all
select 'z','20180305'
select s.id,c.firstname,
c.lastname
from #id as s
cross join #cust as c
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