Update a special id in hierarchical employee table - sql-server

I have an update that has to be made. And Im really stuck.
This is a classic hierarchical employee table question, but with a twist.
Please look at this tree of users:
employees
(Completely unrelated to my problem, just something I found with google pictures)
Lets assume the following Id's:
RM1: EmpId 1, ParentId null
AM1: EmpId 2, ParentId 1
MGR1: EmpId 3, ParentId 2
MGR2: EmpId 4, ParentId 2
EMP1: EmpId 5, ParentId 3
EMP2: EmpId 6, ParentId 3
EMP3: EmpId 7, ParentId 4
EMP4: EmpId 8, ParentId 4
I need to add another column, lets call it parentSpecialId.
This id is the id of the user below root (AM1 and AM2).
All users below AM1 and AM2 should have the parentSpecialId set to the user below root.
Which gives us:
RM1: EmpId 1, ParentId null parentSpecialId null
AM1: EmpId 2, ParentId 1 parentSpecialId null
MGR1: EmpId 3, ParentId 2 parentSpecialId 2
MGR2: EmpId 4, ParentId 2 parentSpecialId 2
EMP1: EmpId 5, ParentId 3 parentSpecialId 2
EMP2: EmpId 6, ParentId 3 parentSpecialId 2
EMP3: EmpId 7, ParentId 4 parentSpecialId 2
EMP4: EmpId 8, ParentId 4 parentSpecialId 2
All I have is this CTE which gives me a result set with AM1 and AM2.
So I need to traverse all way down to EMPX and update parentSpecialId with Id 2 for
AM1 and the same for all users for AM2. Of course, it needs to by dynamic, in real life I have 12 of these users below root.
Does it make sense?
Here is my CTE:
WITH EmpsCTE AS
(
SELECT id, parent, name, 0 AS EmployeeLevel
FROM Employee
WHERE parent = 0
UNION ALL
SELECT e.id, e.parent, e.name, EmployeeLevel + 1
FROM EmpsCTE AS p
JOIN Employee AS e ON e.parent = p.id
)
SELECT id, parent, name, EmployeeLevel
From EmpsCTE where EmployeeLevel = 1
Oh, and I use Sql server 2008 R2

Sample data:
declare #T table
(
Name varchar(10),
EmpId int,
ParentId int,
ParentSpecialID int
);
insert into #T(Name, EmpId, ParentId) values
('RM1', 1, null),
('AM1', 2, 1),
('MGR1', 3, 2),
('MGR2', 4, 2),
('EMP1', 5, 3),
('EMP2', 6, 3),
('EMP3', 7, 4),
('EMP4', 8, 4);
Update statement:
with C as
(
select T3.EmpId,
T2.EmpId as ParentSpecialId
from #T as T1
inner join #T as T2
on T1.EmpId = T2.ParentId
inner join #T as T3
on T2.EmpId = T3.ParentId
where T1.ParentId is null
union all
select T.EmpId,
C.ParentSpecialId
from #T as T
inner join C
on T.ParentId = C.EmpId
)
update T
set ParentSpecialId = C.ParentSpecialId
from #T as T
inner join C
on T.EmpId = C.EmpId

To handle a tree of arbitrary depth:
declare #T table ( Name varchar(16), EmpId int, ParentId int );
insert into #T(Name, EmpId, ParentId) values
('RM1', 1, null),
('AM1', 2, 1),
('MGR1', 3, 2),
('MGR2', 4, 2),
('EMP1', 5, 3),
('EMP2', 6, 3),
('EMP3', 7, 4),
('EMP4', 8, 4),
('AM2', 9, 1),
('MGR3', 10, 9),
('EMP5', 11, 10),
('Brown Noser', 12, 11),
('Intern', 13, 12),
('Coop', 14, 13),
('Nephew', 15, 14),
('Contractor', 16, 15);
; with CTE as (
-- Start with the root(s).
select Name, EmpId, ParentId, 0 as Depth, Cast(NULL as Int) as parentSpecialId
from #T
where ParentId is NULL
union all
-- Add the direct reports one layer at a time.
select T.Name, T.EmpId, T.ParentId, CTE.Depth + 1, case when CTE.Depth = 1 then T.ParentId else CTE.parentSpecialId end
from CTE inner join
#T as T on T.ParentId = CTE.EmpID
where T.ParentId = CTE.EmpId
)
select *,
( select Name from CTE as R where R.EmpId = CTE.ParentId ) as ReportsTo,
( select Name from CTE as SC where SC.EmpId = CTE.parentSpecialId ) as SubCommander
from CTE
order by Depth, Name
With thanks to Mikael Eriksson for setting up the sample data!

Related

How to get the Previous & next row based on condition

I am trying to get the statement on fetching the previous and next rows of a selected row.
Declare #OderDetail table
(
Id int primary key,
OrderId int,
ItemId int,
OrderDate DateTime2,
Lookup varchar(15)
)
INSERT INTO #OderDetail
VALUES
(1, 10, 1, '2018-06-11', 'A'),
(2, 10, 2, '2018-06-11', 'BE'), --this
(3, 2, 1, '2018-06-04', 'DR'),
(4, 2, 2, '2018-06-04', 'D'), --this
(5, 3, 2, '2018-06-14', 'DD'), --this
(6, 4, 2, '2018-06-14', 'R');
DECLARE
#ItemId int = 2,
#orderid int = 10
Required output:
Input for the procedure is order id =10 and item id =2 and i need to check item-2 is in the any other order i.e only previous and next item of matched record/order as per order date
Is this what your after? (Updated to reflect edit [OrderDate] to question)
Declare #OderDetail table
(
Id int primary key,
OrderId int,
ItemId int,
OrderDate DateTime2,
Lookup varchar(15)
)
INSERT INTO #OderDetail
VALUES
(1, 10, 1, '2018-06-11', 'A'),
(2, 10, 2, '2018-06-11', 'BE'), --this
(3, 2, 1, '2018-06-04', 'DR'),
(4, 2, 2, '2018-06-04', 'D'), --this
(5, 3, 2, '2018-06-14', 'DD'), --this
(6, 4, 2, '2018-06-14', 'R');
declare #ItemId int=2 , #orderid int = 10;
Query
With cte As
(
Select ROW_NUMBER() OVER(ORDER BY OrderDate) AS RecN,
*
From #OderDetail Where ItemId=#ItemId
)
Select Id, OrderId, ItemId, [Lookup] From cte Where
RecN Between ((Select Top 1 RecN From cte Where OrderId = #orderid) -1) And
((Select Top 1 RecN From cte Where OrderId = #orderid) +1)
Order by id
Result:
Id OrderId ItemId Lookup
2 10 2 BE
4 2 2 D
5 3 2 DD
Another possible approach is to use LAG() and LEAD() functions, that return data from a previous and subsequent row form the same resul tset.
-- Table
DECLARE #OrderDetail TABLE (
Id int primary key,
OrderId int,
ItemId int,
OrderDate DateTime2,
Lookup varchar(15)
)
INSERT INTO #OrderDetail
VALUES
(1, 10, 1, '2018-06-11', 'A'),
(2, 10, 2, '2018-06-11', 'BE'), --this
(3, 2, 1, '2018-06-04', 'DR'),
(4, 2, 2, '2018-06-04', 'D'), --this
(5, 3, 2, '2018-06-14', 'DD'), --this
(6, 4, 2, '2018-06-14', 'R');
-- Item and order
DECLARE
#ItemId int = 2,
#orderid int = 10
-- Statement
-- Get previois and next ID for every order, grouped by ItemId, ordered by OrderDate
;WITH cte AS (
SELECT
Id,
LAG(Id, 1) OVER (PARTITION BY ItemId ORDER BY OrderDate) previousId,
LEAD(Id, 1) OVER (PARTITION BY ItemId ORDER BY OrderDate) nextId,
ItemId,
OrderId,
Lookup
FROM #OrderDetail
)
-- Select current, previous and next order
SELECT od.*
FROM cte
CROSS APPLY (SELECT * FROM #OrderDetail WHERE Id = cte.Id) od
WHERE (cte.OrderId = #orderId) AND (cte.ItemId = #ItemId)
UNION ALL
SELECT od.*
FROM cte
CROSS APPLY (SELECT * FROM #OrderDetail WHERE Id = cte.previousId) od
WHERE (cte.OrderId = #orderId) AND (cte.ItemId = #ItemId)
UNION ALL
SELECT od.*
FROM cte
CROSS APPLY (SELECT * FROM #OrderDetail WHERE Id = cte.nextId) od
WHERE (cte.OrderId = #orderId) AND (cte.ItemId = #ItemId)
Output:
Id OrderId ItemId OrderDate Lookup
2 10 2 11/06/2018 00:00:00 BE
4 2 2 04/06/2018 00:00:00 D
5 3 2 14/06/2018 00:00:00 DD
Update to given this data set: I see where you are going with this. Note that in SOME cases there IS no row before the given one - so it only returns 2 not 3. Here I updated the CTE version. Un-comment the OTHER row to see 3 not 2 as there is then one before the selected row with that Itemid.
Added a variable to demonstrate how this is better allowing you to get 1 before and after or 2 before/after if you change that number (i.e. pass a parameter) - and if less rows, or none are before or after it gets as many as it can within that constraint.
Data setup for all versions:
Declare #OderDetail table
(
Id int primary key,
OrderId int,
ItemId int,
OrderDate DateTime2,
Lookup varchar(15)
)
INSERT INTO #OderDetail
VALUES
(1, 10, 1, '2018-06-11', 'A'),
(2, 10, 2, '2018-06-11', 'BE'), --this
(3, 2, 1, '2018-06-04', 'DR'),
(4, 2, 2, '2018-06-04', 'D'), --this
(5, 3, 2, '2018-06-14', 'DD'), --this
(9, 4, 2, '2018-06-14', 'DD'),
(6, 4, 2, '2018-06-14', 'R'),
--(10, 10, 2, '2018-06-02', 'BE'), -- un-comment to see one before
(23, 4, 2, '2018-06-14', 'R');
DECLARE
#ItemId int = 2,
#orderid int = 2;
CTE updated version:
DECLARE #rowsBeforeAndAfter INT = 1;
;WITH cte AS (
SELECT
Id,
OrderId,
ItemId,
OrderDate,
[Lookup],
ROW_NUMBER() OVER (ORDER BY OrderDate,Id) AS RowNumber
FROM #OderDetail
WHERE
ItemId = #itemId -- all matches of this
),
myrow AS (
SELECT TOP 1
Id,
OrderId,
ItemId,
OrderDate,
[Lookup],
RowNumber
FROM cte
WHERE
ItemId = #itemId
AND OrderId = #orderid
)
SELECT
cte.Id,
cte.OrderId,
cte.ItemId,
cte.OrderDate,
cte.[Lookup],
cte.RowNumber
FROM ctE
INNER JOIN myrow
ON ABS(cte.RowNumber - myrow.RowNumber) <= #rowsBeforeAndAfter
ORDER BY OrderDate, OrderId;
You probably want the CTE method (See an original at the end of this) however:
Just to point out, this gets the proper results but is probably not what you are striving for since it is dependent on the row order and the item id not the actual row with those two values:
SELECT TOP 3
a.Id,
a.OrderId,
a.ItemId,
a.Lookup
FROM #OderDetail AS a
WHERE
a.ItemId = #ItemId
To fix that, you can use an ORDER BY and TOP 1 with a UNION, kind of ugly. (UPDATED with date sort and != on the id.)
SELECT
u.Id,
u.OrderId,
u.OrderDate,
u.ItemId,
u.Lookup
FROM (
SELECT
a.Id,
a.OrderId,
a.OrderDate,
a.ItemId,
a.Lookup
FROM #OderDetail AS a
WHERE
a.ItemId = #ItemId
AND a.OrderId = #orderid
UNION
SELECT top 1
b.Id,
b.OrderId,
b.OrderDate,
b.ItemId,
b.Lookup
FROM #OderDetail AS b
WHERE
b.ItemId = #ItemId
AND b.OrderId != #orderid
ORDER BY b.OrderDate desc, b.OrderId
UNION
SELECT top 1
b.Id,
b.OrderId,
b.OrderDate,
b.ItemId,
b.Lookup
FROM #OderDetail AS b
WHERE
b.ItemId = #ItemId
AND b.OrderId != #orderid
ORDER BY b.OrderDate asc, b.OrderId
) AS u
ORDER BY u.OrderDate asc, u.OrderId
I think its simple, you can check with min(Id) and Max(id) with left outer join or outer apply
like
Declare #ItemID int = 2
Select * From #OderDetail A
Outer Apply (
Select MIN(A2.Id) minID, MAX(A2.Id) maxID From #OderDetail A2
Where A2.ItemId =#ItemID
) I05
Outer Apply(
Select * From #OderDetail Where Id=minID-1
Union All
Select * From #OderDetail Where Id=maxID+1
) I052
Where A.ItemId =#ItemID Order By A.Id
Let me know if this helps you or you face any problem with it...
Regards,

sql server how to retrieve a full tree given any child?

I have a table with id and parent_id. I have seen many CTE on how to generate the tree. However I can't seem to filter the tree in order to retrieve a full tree given any node from it.
Given the values
1, NULL
2, 1
3, 2
4, 2
5, NULL
6, 5
7, NULL
If I filter by id any of the values 1,2,3 or 4 I should get the tree
1, NULL
2, 1
3, 2
4, 2
for 5 or 6
5, NULL
6, 5
for 7
7, NULL
Can this be achieved using CTE?
Use one CTE to locate the root of the tree, and then use a second CTE to explode the tree structure:
declare #T table (ID int not null, Parent int null)
insert into #T(ID,Parent) values
(1, NULL),
(2, 1 ),
(3, 2 ),
(4, 2 ),
(5, NULL),
(6, 5 ),
(7, NULL)
declare #Node int
set #node = 3
;With Root as (
select t.ID,t.Parent from #T t where t.ID = #Node or t.Parent = #Node
union all
select t.ID,t.Parent
from
Root r
inner join
#t t
on
t.ID = r.Parent
), Tree as (
select ID,Parent from Root where Parent is null
union all
select t.ID,t.Parent
from #T t
inner join
Tree tr
on tr.ID = t.Parent
)
select * from Tree
Result:
ID Parent
----------- -----------
1 NULL
2 1
3 2
4 2
Hopefully you can see how the two CTEs are working in opposite directions.
You could use recursive CTE like this.
CTE returns all tree with RootId, and you could get all tree nodes by its RootId
DECLARE #SampleData AS TABLE
(
NodeId int,
ParentNodeId int
)
INSERT INTO #SampleData
(
NodeId,
ParentNodeId
)
VALUES
( 1, NULL),
( 2, 1),
( 3, 2),
( 4, 2),
( 5, NULL),
( 6, 5),
( 7, NULL)
DECLARE #NodeId int = 4
-- temp returns all child nodes of rootid (1,5,7)
;WITH temp AS
(
SELECT sd.NodeId, sd.ParentNodeId, sd.NodeId AS RootId
FROM #SampleData sd
WHERE sd.ParentNodeId IS NULL
UNION ALL
SELECT sd.NodeId, sd.ParentNodeId, t.RootId
FROM temp t
INNER JOIN #SampleData sd ON t.NodeId = sd.ParentNodeId
)
SELECT t2.NodeId, t2.ParentNodeId
FROM temp t
INNER JOIN temp t2 ON t2.RootId = t.RootId
WHERE t.NodeId = #NodeId
OPTION (MAXRECURSION 0)
Demo link: http://rextester.com/PPPMXX4941

Copy previous row value to all next rows in sql

I have requirement to copy previous rows values to next all rows in sql server.
With LAG function, I can achieve this one only for next row. but I have to copy more than rows.
Here is sample example :
Query:
SELECT
t1.ID,
t1.CustID,
t2.ID,
t2.CustID,
t2.Flag,
COALESCE(
t2.Flag,
(
SELECT TOP 1 l.Flag
FROM TBL2 l
WHERE l.CustID = t1.CustID AND l.ID < t1.ID
ORDER BY l.ID desc
)) as 'Final'
FROM
TBL1 t1
LEFT OUTER JOIN TBL2 t2 ON t2.ID = t1.ID AND t2.CustID = t1.CustID
ORDER BY
t1.CustID,
t1.ID desc
Setup:
CREATE TABLE TBL1 (ID int, CustID int)
GO
CREATE TABLE TBL2 (ID int, CustID int, Flag bit)
GO
INSERT INTO TBL1 (ID, CustID)
SELECT 1, 11 UNION ALL
SELECT 1, 12 UNION ALL
SELECT 1, 13 UNION ALL
SELECT 1, 14 UNION ALL
SELECT 1, 15 UNION ALL
SELECT 1, 16 UNION ALL
SELECT 1, 17 UNION ALL
SELECT 2, 11 UNION ALL
SELECT 2, 12 UNION ALL
SELECT 2, 13 UNION ALL
SELECT 2, 14 UNION ALL
SELECT 2, 15 UNION ALL
SELECT 2, 16 UNION ALL
SELECT 2, 17 UNION ALL
SELECT 3, 11 UNION ALL
SELECT 3, 12 UNION ALL
SELECT 3, 13 UNION ALL
SELECT 3, 14 UNION ALL
SELECT 3, 15 UNION ALL
SELECT 3, 16 UNION ALL
SELECT 3, 17 UNION ALL
SELECT 4, 11 UNION ALL
SELECT 4, 12 UNION ALL
SELECT 4, 13 UNION ALL
SELECT 4, 14 UNION ALL
SELECT 4, 15 UNION ALL
SELECT 4, 16 UNION ALL
SELECT 4, 17
GO
INSERT INTO TBL2 (ID, CustID, Flag)
SELECT 1, 11, 0 UNION ALL
SELECT 1, 12, 1 UNION ALL
SELECT 1, 13, 1 UNION ALL
SELECT 1, 14, 0 UNION ALL
SELECT 1, 15, 0 UNION ALL
SELECT 1, 16, 0 UNION ALL
SELECT 1, 17, 1 UNION ALL
SELECT 2, 11, 1 UNION ALL
SELECT 2, 13, 0 UNION ALL
SELECT 2, 14, 1 UNION ALL
SELECT 2, 15, 1 UNION ALL
SELECT 2, 17, 0 UNION ALL
SELECT 3, 13, 1 UNION ALL
SELECT 3, 15, 0 UNION ALL
SELECT 3, 17, 1 UNION ALL
SELECT 4, 12, 0 UNION ALL
SELECT 4, 17, 0
GO
You can try this:
SELECT *
,ISNULL(b.flag,(MAX(b.flag) OVER (PARTITION BY a.[custid] ORDER BY b.[id] DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)))
FROM #TBL1 A
LEFT JOIN #TBL2 B
ON A.id = b.id
AND A.custid = b.custid
ORDER BY a.[custid] ASC, a.[id] DESC
Here is the sample data:
DECLARE #TBL1 TABLE
(
[id] TINYINT
,[custid] TINYINT
);
DECLARE #TBL2 TABLE
(
[id] TINYINT
,[custid] TINYINT
,[flag] TINYINT
);
INSERT INTO #TBL1([id], [custid])
VALUES
(1,11)
,(1,12)
,(1,13)
,(1,14)
,(1,15)
,(1,16)
,(1,17)
,(2,11)
,(2,12)
,(2,13)
,(2,14)
,(2,15)
,(2,16)
,(2,17)
,(3,11)
,(3,12)
,(3,13)
,(3,14)
,(3,15)
,(3,16)
,(3,17)
,(4,11)
,(4,12)
,(4,13)
,(4,14)
,(4,15)
,(4,16)
,(4,17);
INSERT INTO #TBL2 ([id], [custid], [flag])
VALUES (1,11,0)
,(1,12,1)
,(1,13,1)
,(1,14,0)
,(1,15,0)
,(1,16,0)
,(1,17,1)
,(2,11,1)
,(2,13,0)
,(2,14,1)
,(2,15,1)
,(2,17,0)
,(3,13,1)
,(3,15,0)
,(3,17,1)
,(4,12,0)
,(4,17,0)
You can use a LEFT JOIN ,a CASE EXPRESSION and a correlated query :
SELECT t.id,t.custid,s.id,s.custid,
COALESCE(s.flag,(SELECT TOP 1 ss.flag FROM tbl2 ss
WHERE ss.custid = t.custid and ss.id < t.id
ORDER BY ss.id DESC)) as flag
FROM Tbl1 t
LEFT JOIN tbl2 s
ON(t.custid = s.custid and s.id = t.id)

PIVOT and several aggregates in sql server 2014

My current understanding is that it is impossible to have several aggregates - such as an additional SUM(Measure2) in the following example:
IF OBJECT_ID('tempdb..#Dim1') IS NOT NULL DROP TABLE #Dim1
IF OBJECT_ID('tempdb..#Dim2') IS NOT NULL DROP TABLE #Dim2
IF OBJECT_ID('tempdb..#Facts') IS NOT NULL DROP TABLE #Facts
CREATE TABLE #Dim1
(
Id INT IDENTITY(1,1),
DimName NVARCHAR(100)
)
CREATE TABLE #Dim2
(
Id INT IDENTITY(1,1),
DimName NVARCHAR(100)
)
CREATE TABLE #Facts
(
Id INT IDENTITY(1,1),
Dim1Id INT,
Dim2Id INT,
Measure1 FLOAT,
Measure2 FLOAT
)
INSERT INTO #Dim1
SELECT N'Dim1Name1'
UNION ALL
SELECT N'Dim1Name2'
UNION ALL
SELECT N'Dim1Name3'
INSERT INTO #Dim2
SELECT N'Dim2Name1'
UNION ALL
SELECT N'Dim2Name2'
INSERT INTO #Facts
SELECT 1, 2, 2, 10
UNION ALL
SELECT 1, 2, 10, 3
UNION ALL
SELECT 1, 1, 1, 56
UNION ALL
SELECT 2, 1, 5, 4
UNION ALL
SELECT 2, 2, 4, 4
UNION ALL
SELECT 3, 1, 4, 1
UNION ALL
SELECT 3, 1, 20, 56
;WITH CTE1 AS
(
SELECT
Facts.Measure1,
Dimensions1.DimName AS DimName1,
Dimensions2.DimName AS DimName2
FROM #Facts AS Facts
INNER JOIN #Dim1 AS Dimensions1 ON Facts.Dim1Id = Dimensions1.Id
INNER JOIN #Dim2 AS Dimensions2 ON Facts.Dim2Id = Dimensions2.Id
)
SELECT
*
FROM CTE1
PIVOT
(
SUM(Measure1)
FOR DimName2
IN([Dim2Name1], [Dim2Name2])
) AS X;
Is this really the case and do I have to use the old 'MAX CASE' approach for these scenarios?
Is this helpful?
;WITH CTE1 AS
(
SELECT
Facts.Measure1,
Facts.Measure2,
Dimensions1.DimName AS DimName1,
Dimensions2.DimName AS DimName2
FROM #Facts AS Facts
INNER JOIN #Dim1 AS Dimensions1 ON Facts.Dim1Id = Dimensions1.Id
INNER JOIN #Dim2 AS Dimensions2 ON Facts.Dim2Id = Dimensions2.Id
)
SELECT
DimName1,
CAST(SUBSTRING(X.[Dim2Name1], 1, 10) AS INT) Dim2Name1Measure1,
CAST(SUBSTRING(X.[Dim2Name1], 11, 10) AS INT) Dim2Name1Measure2,
CAST(SUBSTRING(X.[Dim2Name2], 1, 10) AS INT) Dim2Name2Measure1,
CAST(SUBSTRING(X.[Dim2Name2], 11, 10) AS INT) Dim2Name2Measure2
FROM
(
SELECT
CAST(SUM(Measure1) AS CHAR(10)) + CAST(SUM(Measure2) AS CHAR(10)) AS Combined,
DimName1,
DimName2
FROM CTE1
GROUP BY
DimName1,
DimName2
) S
PIVOT
(
MAX(Combined)
FOR DimName2
IN([Dim2Name1], [Dim2Name2])
) AS X;

How to find the parent ID in a return table

I have following table:
ID ParentID
1 NULL
2 1
3 2
4 NULL
5 4
6 5
7 3
I want to find the first ID of a specific child ID.
Example: ID=7 and the result is 1
ID=6 and the result is 4
How to do it?
You need to do a bit of recursive CTE magic to solve this one.
Given the data in a table variable thusly:
declare #data table(id int, parentid int)
insert into #data
select 1, null
union select 2,1
union select 3,2
union select 4, null
union select 5,4
union select 6,5
union select 7,3
The following should do the trick:
;with recursiveTable as
(
select d.id, d.parentId, 0 as depth
from #data d
where d.id=6 -- Parameterize this
union all
select d.id, d.parentid, r.depth-1
from #data d
inner join recursiveTable r
on d.id = r.parentId
)
select top 1 id
from recursiveTable
order by depth
Plugging 6 in as above returns 4. Changing this to 7 returns 1 as requested.
Try this:
CREATE TABLE childpar(ID int,ParentID int)
INSERT INTO childpar
values(1,NULL),
(2, 1),
(3, 2),
(4, NULL),
(5, 4),
(6, 5),
(7, 3)
DECLARE #inpyID int
SET #inpyID=7
;WITH CTE1 as (
select * from childpar where id=#inpyID
union all
select c2.* from CTE1 c1 inner join childpar c2 on c1.ParentID = c2.ID
)
select top 1 id from CTE1 order by id asc

Resources