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,
Related
I am trying to figure out an efficient way to select the previous order item for each order item in a current order.
For example each order has a set of order items, i.e. the amount of an article/product that was ordered. For a particular customer, this would result in an order history as illustrated below:
I would like to find the "previous" orderitem for each orderitem in a particular order, i.e. the most recent orderitem for the same customer and article product preceding the order in question. These are highlighted above. Note that the previous orderitem may not be from the previous order of that customer.
Selecting it for a particular orderitem in an order with :datetime for :customerID and :articleID could be done like this:
select top(1) *
from orderitem
join order on order.orderid = orderitem.orderid
where order.customerID = :customerID
and order.datetime < :datetime
and orderitem.articleID = :articleID
order by order.datetime desc
However, is there an efficient way rather than looping or using a sub-select to select all previous orderitems for a given order with a single select or some type of join?
Your description of your desired outcome isn't quite clear. This sounds like you want a pivot table for the customer with the most recent order on the left. Here's an example of using PIVOT to accomplish the desired output. It only has order dates, if you want order numbers, that can be added in the column names.
--Create tables for test data.
CREATE TABLE order_hdr (
order_number int
, order_date date
, customer_id int
);
CREATE TABLE order_lines (
order_number int
, line_number int
, quantity int
, item_id nvarchar(50)
);
--Populate test data.
INSERT INTO order_hdr (order_number, order_date, customer_id)
VALUES (111, '1/1/2022', 12345)
, (112, '1/2/2022', 45678)
, (113, '1/2/2022', 87964)
, (114, '1/3/2022', 12345)
, (115, '1/3/2022', 45678)
, (116, '1/3/2022', 87964)
, (117, '1/9/2022', 12345)
, (118, '1/9/2022', 45678)
, (119, '1/9/2022', 87964)
;
INSERT INTO order_lines (order_number, line_number, quantity, item_id)
VALUES (111, 1, 4, 'Article A')
, (111, 2, 2, 'Article B')
, (111, 3, 3, 'Article C')
, (111, 4, 1, 'Article D')
, (112, 1, 1, 'Article B')
, (112, 2, 1, 'Article C')
, (112, 3, 1, 'Article D')
, (113, 1, 11, 'Article B')
, (113, 2, 9, 'Article C')
, (113, 3, 8, 'Article D')
, (114, 1, 4, 'Article C')
, (114, 2, 6, 'Article D')
, (115, 1, 77, 'Article A')
, (115, 2, 22, 'Article B')
;
--Variables for filtering data.
DECLARE #CustID int = 12345;
DECLARE #ReportDate date = '2/1/2022';
DECLARE #ItemID nvarchar(50) = 'Article A'; --Not used.
--Create a variable to hold the string of dates.
DECLARE #dates nvarchar(max);
--Get last 5 distinct order dates.
SET #dates = STUFF((
SELECT ',"' + CAST(order_date as nvarchar) + '"'
FROM (
SELECT DISTINCT TOP (5) order_date
FROM order_hdr
WHERE customer_id = #CustID
ORDER BY order_date DESC
) as prelim
FOR XML PATH('')
),1,1,'');
--For debugging, show the string of dates.
PRINT 'Date string contents: ' + #dates;
--For debugging/testing, here is a static query using PIVOT
PRINT 'Test Query using Static Order Dates:';
SELECT item_id, "1/3/2022", "1/2/2022"
FROM (
SELECT
oh.order_date
, ol.item_id
, ol.quantity
FROM order_hdr as oh
INNER JOIN order_lines as ol
ON ol.order_number = oh.order_number
WHERE oh.customer_id = #CustID
) as st
PIVOT
(
SUM(quantity)
FOR order_date IN ("1/3/2022", "1/2/2022")
) as pt
;
--Now create a text variable to hold the dynamic SQL. We will substitue the place holders for the order dates or order numbers.
DECLARE #sqlText nvarchar(max) = N'
SELECT item_id, {{0}}
FROM (
SELECT
oh.order_date
, ol.item_id
, ol.quantity
FROM order_hdr as oh
INNER JOIN order_lines as ol
ON ol.order_number = oh.order_number
WHERE oh.customer_id = {{1}}
) as st
PIVOT
(
SUM(quantity)
FOR order_date IN ({{0}})
) as pt
;
';
--Replace the placeholders with the date string;
SET #sqlText = REPLACE(#sqlText, '{{0}}', #dates);
--Replace the placeholders with the date string;
SET #sqlText = REPLACE(#sqlText, '{{1}}', CAST(#CustID as nvarchar));
--Show query for debugging.
PRINT 'The dynamic query: ' + CHAR(13) + #sqlText;
--Execute the dynamic sql.
PRINT 'Final Query using Dynamic Order Dates:';
EXEC (#sqlText);
Test Query using Static Order Dates:
item_id
1/3/2022
1/2/2022
Article A
null
null
Article B
null
null
Article C
4
null
Article D
6
null
Date string contents: "2022-01-09","2022-01-03","2022-01-01"
The dynamic query:
SELECT item_id, "2022-01-09","2022-01-03","2022-01-01"
FROM (
SELECT
oh.order_date
, ol.item_id
, ol.quantity
FROM order_hdr as oh
INNER JOIN order_lines as ol
ON ol.order_number = oh.order_number
WHERE oh.customer_id = 12345
) as st
PIVOT
(
SUM(quantity)
FOR order_date IN ("2022-01-09","2022-01-03","2022-01-01")
) as pt
;
Final Query using Dynamic Order Dates:
item_id
2022-01-09
2022-01-03
2022-01-01
Article A
null
null
4
Article B
null
null
2
Article C
null
4
3
Article D
null
6
1
fiddle
DECLARE #tmp1 TABLE (ItemCode INT, Item VARCHAR(30), Qty INT)
INSERT INTO #tmp1 (ItemCode, Item, Qty)
VALUES(1, 'Item1', 300),
(2, 'Item2', 500)
DECLARE #tmp2 TABLE (JobNo INT, ItemCode INT, Item VARCHAR(30), Qty INT)
INSERT INTO #tmp2 (JobNo, ItemCode, Item, Qty)
VALUES(1, 1, 'Item1', 150),
(2, 1, 'Item1', 150),
(3, 2, 'Item1', 50),
(4, 2, 'Item1', 75),
(5, 2, 'Item1', 125),
(6, 2, 'Item1', 100)
;WITH MyCTE AS
(
SELECT t1.ItemCode, t1.Item, t1.Qty, t2.JobNo, t2.ItemCode AS ItemCode2, t2.Item AS Item2, t2.Qty AS Qty2, t2.MySeed
FROM #tmp1 AS t1 INNER JOIN (
SELECT *, ROW_NUMBER() OVER(PARTITION BY ItemCode ORDER BY JobNo) AS [MySeed]
FROM #tmp2
) AS t2 ON t1.ItemCode = t2.ItemCode
WHERE t2.MySeed = 1
UNION ALL
SELECT NULL AS ItemCode, NULL AS Item, NULL AS Qty, t2.JobNo, t2.ItemCode AS ItemCode2, t2.Item AS Item2, t2.Qty AS Qty2, t2.MySeed
FROM #tmp1 AS t1 INNER JOIN (
SELECT *, ROW_NUMBER() OVER(PARTITION BY ItemCode ORDER BY JobNo) AS [MySeed]
FROM #tmp2
) AS t2 ON t1.ItemCode = t2.ItemCode
WHERE t2.MySeed > 1
)
SELECT *
FROM MyCTE
ORDER BY ItemCode2, MySeed
I found a sample query online as above which is straight forward as it combines table 1 and table 2 and it assumes in table 2 if myseed count is 1 then input null values.
But, I need a query in snowflake for the scenario where table 1 can have any number of entries for each id and table 2 can also have multiple entries for each id. I need to join both the tables without duplicates and input null in the rows when the one table has multiple entries and other table don't have multiple entries.
How to write in snowflake(SNOWSQL)?
I need a result like below to combine two tables and avoid duplicates in either one of the table
I am trying to map sub categories to a 'majority' parent category. Looking at this example:
IF OBJECT_ID(N'tempdb..#Temp') IS NOT NULL DROP TABLE #Temp
CREATE TABLE #Temp
(
ID INT,
SubCategory NVARCHAR(100),
Category NVARCHAR(100)
)
INSERT INTO #Temp
SELECT 1, N'SC1', N'C1'
UNION
SELECT 2, N'SC1', N'C1'
UNION
SELECT 3, N'SC1', N'C1'
UNION
SELECT 4, N'SC1', N'C2'
UNION
SELECT 5, N'SC1', N'C2'
sub category SC1 can belong to 2 'parent' categories: C1, C2 but C1 is assigned more to SC1 than C2 so it represents the majority. I am currently using a correlated sub query to map sun categories to their majority parent categroy like this:
IF OBJECT_ID(N'tempdb..#Temp') IS NOT NULL DROP TABLE #Temp
CREATE TABLE #Temp
(
ID INT,
SubCategory NVARCHAR(100),
Category NVARCHAR(100)
)
INSERT INTO #Temp
SELECT 1, N'SC1', N'C1'
UNION
SELECT 2, N'SC1', N'C1'
UNION
SELECT 3, N'SC1', N'C1'
UNION
SELECT 4, N'SC1', N'C2'
UNION
SELECT 5, N'SC1', N'C2'
SELECT
Id,
OuterQuery.SubCategory,
Category = (SELECT TOP 1 Category FROM #Temp AS InnerQuery
WHERE InnerQuery.SubCategory = OuterQuery.SubCategory
GROUP BY InnerQuery.SubCategory, InnerQuery.Category
ORDER BY COUNT(DISTINCT Id) DESC)
FROM #Temp AS OuterQuery
Is this a correct way of achieving want I want to?
I would do it like this:
DECLARE #t TABLE (
ID INT,
SubCategory NVARCHAR(100),
Category NVARCHAR(100)
)
INSERT INTO #t
VALUES
( 1, N'SC1', N'C1'),
(2, N'SC1', N'C1'),
(3, N'SC1', N'C1'),
(4, N'SC1', N'C2'),
(5, N'SC1', N'C2'),
(6, N'SC2', N'C2'),
(7, N'SC2', N'C2'),
(8, N'SC2', N'C3');
WITH a AS (
SELECT
subcategory,
category,
COUNT(id) cnt
FROM #t
GROUP BY
SubCategory,
category
), b as (
SELECT
subcategory,
category,
ROW_NUMBER() OVER
(PARTITION BY subcategory ORDER BY cnt DESC) row
FROM a
)
SELECT
subcategory,
category AS Majority
FROM b
WHERE row = 1
This produces the result:
subcategory Majority
SC1 C1
SC2 C2
There's this way with just subqueries;
Test Data (I've increased the range for this)
IF OBJECT_ID(N'tempdb..#Temp') IS NOT NULL DROP TABLE #Temp
CREATE TABLE #Temp
(ID INT, SubCategory NVARCHAR(100), Category NVARCHAR(100))
INSERT INTO #Temp (ID, SubCategory, Category)
VALUES
(1, N'SC1', N'C1')
,(2, N'SC1', N'C1')
,(3, N'SC1', N'C1')
,(4, N'SC1', N'C2')
,(5, N'SC1', N'C2')
,(6, N'SC2', N'C3')
,(7, N'SC2', N'C3')
,(8, N'SC2', N'C2')
,(9, N'SC2', N'C2')
,(10, N'SC2', N'C2')
,(11, N'SC2', N'C2')
Now the query;
SELECT DISTINCT
t.SubCategory
,t.Category
FROM #Temp t
JOIN
(
SELECT
SubCategory
,Category
,COUNT(SubCategory) Ct
FROM #Temp
GROUP BY SubCategory, Category
) Cont
ON t.SubCategory = cont.SubCategory
AND t.Category = cont.Category
JOIN
(
SELECT
a.SubCategory
,MAX(a.Ct) MaxCount
FROM
(
SELECT
SubCategory
,Category
,COUNT(SubCategory) Ct
FROM #Temp
GROUP BY SubCategory, Category
) a
GROUP BY a.SubCategory
) maxi ON t.SubCategory = maxi.SubCategory
AND cont.Ct = maxi.MaxCount
I have a chart like picture , that store it in table with KID , ParentID .
how can i get max MR for all child under parent.
example : for Node C ----> max ( MR(D) , MR(E) , MR(F) )
How can find Max(MR) for all child of node?
DECLARE #a TABLE
(
KID INT PRIMARY KEY,
ParentID INT,
MR INT
)
INSERT INTO #a (KID, ParentID, MR)
VALUES
(1, 0, 3), (2, 1, 1), (3, 1, 3),
(4, 3, 3), (5, 3, 5), (6, 5, 3)
;WITH cte AS
(
SELECT *
FROM #a
WHERE ParentID = 3
UNION ALL
SELECT t2.*
FROM cte t1
JOIN #a t2 ON t1.ParentID = t2.KID
)
SELECT MAX(MR)
FROM cte
OPTION (MAXRECURSION 0)
result -
5
Maybe you can use over clause
SELECT
ParentID,
MAX(MR) OVER(PARTITION BY ParentID)
FROM
Table
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!