Combining two columns based on match in other colums in two tables - sql

I have two tables, each containing two identical columns and one column unique for that table. What I need to do is combine those tables, with combinations of those unique columns for each matching pair of identical columns as result. Example of what I mean:
ACC ACTION PRIORITY ACC ACTION TARGET
A 1 10 A 1 i
A 2 15 A 1 j
A 3 25 A 3 k
B 3 101 B NULL l
B 4 102 B 4 m
B 5 103 B 1 n
ACC and ACTION are columns in both tables. ORDER is unique for the left one, TARGET for the right one. I need to get combinations of ORDER and TARGET on rows where ACC and ACTION match - for example when ACC is A and ACTION is 1, PRIORITY is 10, and TARGET is I or j, therefore combinations would be "10 I" and "10 j".
Also, when ACTION is null in right table, there should be row with the top PRIORITY on that TARGET.
So, expected result:
PRIORITY TARGET
10 i
10 j
25 k
102 m
103 l
Any attempt to do a correct JOIN or so failed from my side.
What I tried:
INSERT INTO #RESULT(TARGET, PRIORITY)
SELECT R.TARGET, MAX(L.PRIORITY)
FROM LEFT_TABLE L INNER JOIN RIGHT_TABLE R
ON L.ACC=R.ACC AND (L.ACTION = R.ACTION OR R.ACTION IS NULL);
But it gives an error. Grouping by TARGET does not make the right output, though.

I used UNION to solve this
SELECT priority, t.target
FROM prio p
JOIN target t ON p.acc = t.acc AND t.action = p.action
UNION
SELECT priority, t.target
FROM prio p
JOIN target t ON p.acc = t.acc AND t.action is null
AND p.priority = (SELECT MAX(priority) FROM prio)

See if this works. I did not test it.
DECLARE #L TABLE(ACC NVARCHAR(10),Action INT,Priority INT)
INSERT #L (ACC,Action,Priority) VALUES ('A',1,10),('A',2,15),('A',3,25),('B',4,101),('B',5,102),('B',6,103)
DECLARE #R TABLE(ACC NVARCHAR(10),Action INT,Target NVARCHAR(10))
INSERT #R (ACC,Action,Target) VALUES ('A',1,'i'),('A',1,'j'),('A',3,'k'),('B',NULL,'l'),('B',4,'m'),('B',1,'n')
SELECT
Target = MAX(R.Target),
Priority = MAX(L.Priority)
FROM
#L L
INNER JOIN #R R ON R.ACC=L.Acc AND (R.ACTION=L.Action OR R.Action IS NULL)
GROUP BY
L.ACC,
L.Action
ORDER BY
MAX(L.Priority)

You can do it like this:
Sample data
create table a
(
acc nvarchar(50),Action1 int, priority1 int)
create table b (acc nvarchar(50),action1 int, target1 nvarchar(50)
)
insert into a
values
('A',1, 10 ),
('A',2, 15 ),
('A',3, 25 ),
('B',3, 101),
('B',4, 102),
('B',5, 103)
insert into dbo.b
values
('A',1,'i'),
('A',1,'j'),
('A',3,'k'),
('B',null,'l'),
('B',4,'m'),
('B',1,'n')
SQL
with data1 as (
select acc,case when action1 is null then maxaction1 else action1 end as action1,target1 from (
select * from dbo.b a
cross apply (select MAX(action1) as MaxAction1 from dbo.a b where a.acc = b.acc ) x
)z
)
select priority1,target1 from data1 a inner join dbo.a b on a.acc = b.acc and a.action1 = b.Action1
Update
Just saw that you wrote you want it on Priority. Then you can do it like this:
SQL Code Update
with data1 as (
select * from (
select y.acc,y.target1,COALESCE(y.action1,c.action1) as Action1 from (
select * from dbo.b a
cross apply (select MAX(priority1) as MaxP from dbo.a b where a.acc = b.acc ) x
)y inner join dbo.a c on c.priority1 = MaxP
)z
)
select priority1,target1 from data1 a inner join dbo.a b on a.acc = b.acc and a.action1 = b.Action1
Result

You can try this:
WITH
tTable1
AS ( SELECT ACC
, [ACTION]
, [PRIORITY]
FROM ( VALUES ('A', 1, 10 )
, ('A', 2, 15 )
, ('A', 3, 25 )
, ('B', 3, 101)
, ('B', 4, 102)
, ('B', 5, 103)
) tTable1(ACC, [ACTION], [PRIORITY])
)
, tTable2
AS ( SELECT ACC
, [ACTION]
, [TARGET]
FROM ( VALUES ('A', 1 , 'i')
, ('A', 1 , 'j')
, ('A', 3 , 'k')
, ('B', NULL, 'l')
, ('B', 4 , 'm')
, ('B', 1 , 'n')
) tTable1(ACC, [ACTION], [TARGET])
)
, tTable1Max
AS ( SELECT ACC
, [PRIORITY] = MAX([PRIORITY])
FROM tTable1
GROUP BY ACC
)
, tResult
AS ( SELECT [PRIORITY] = CASE WHEN tTable2.[ACTION] IS NOT NULL
THEN tTable1.[PRIORITY]
ELSE tTable1Max.[PRIORITY]
END
, tTable2.[TARGET]
FROM tTable2
LEFT JOIN tTable1 ON tTable1.ACC = tTable2.ACC
AND tTable1.[ACTION] = tTable2.[ACTION]
OUTER APPLY ( SELECT [PRIORITY]
FROM tTable1Max
WHERE tTable1Max.ACC = tTable2.ACC
) tTable1Max
)
SELECT *
FROM tResult
WHERE [PRIORITY] IS NOT NULL
ORDER BY [PRIORITY]

Related

Conditional join based on row value

I have department mapping table where I have specific status for specific department but different status for all the rest departments that are not in department table.
dep_table:
country
department
status
FIN
D1
C
FIN
D2
C
FIN
**
O
SWE
D1
C
act_table:
country
department
amt
FIN
D1
16
FIN
D3
45
SWE
D1
13
expected result:
country
department
amt
status
FIN
D1
16
C
FIN
D3
45
O
SWE
D1
13
C
I have this but it causes duplicated rows (because joins ** and also non-** rows):
SELECT t1.country, t1.department, t1.amt, t2.status
FROM act_table t1
LEFT OUTER JOIN dep_table t2
ON t1.country = t2.country
AND CASE WHEN t1.department = t2.department THEN 1 WHEN t2.department = '**' THEN 1 END = 1
(This is very simplified scenario - needs to be done in this manner.)
If I understand correctly, you want 'O' if any of the statuses are 'O'. If there are only two statuses, you can use a correlated subquery:
select a.*,
(select d.status
from dep_table d
where d.country = a.country
order by d.status desc
fetch first 1 row only
) as status
from act_table a;
I think two left joins make it more clear :
with
dep_table (country, department, status) as (
values
('FIN', 'D1', 'C'),
('FIN', 'D2', 'C'),
('FIN', '**', 'O'),
('SWE', 'D1', 'C')
),
act_table (country, department, amt) as (
values
('FIN', 'D2', 16),
('FIN', 'D3', 45),
('SWE', 'D1', 13)
)
select
act.country, act.department, act.amt, coalesce(specific.status, rest.status) status
from act_table act
left join dep_table specific using(country, department)
left join dep_table rest on specific.status is null and (rest.country, rest.department) = (act.country, '**')
order by country, department
Use olap function row_number to select first row
select country , department , amt , status
from
(
select a.country , a.department ,
a.amt , d.status ,
row_number() over( partition by a.country , a.department
order by case when d.department = '**' then 1 else 0 end ) as rn1
from dep_table d ,
act_table a
where d.country = a.country
and ( d.department = a.department or d.department = '**' )
) x
where rn1 = 1

SQL for a Join that contains a condition within one of the join conditions

I have a requirement to do a left join between two tables. TableA is a transactional table while TableB contains reference data. The logical rule for my join is as follow:
SELECT *
FROM
TableA a
LEFT JOIN TableB b
ON a.ItemCode = b.ItemCode
AND a.ItemType = b.ItemType
AND b.FundID = 1 (but if no match found use b.FundID = 99)
The last join condition, the part in brackets, is what I'm having trouble with.
EDIT: Some clarification - If no match is found on ItemCode & ItemType & FundID = 1 then I want to join on ItemCode & ItemType & FundID = 99. Also TableB might have two records that match on both ItemCode and ItemType with one record having a FundID = 1 and the second record having FundID = 2. I that case I only want the record with FundID = 1.
What would be the most efficient way to write this query?
The only thing I can come up with is to execute the query twice, once with FundID = 1 and then with FundID = 99. Then use a set operator to return all the records form the first query and only records from the second query that does not exist in the first one. The code will not be pretty and it does not seem efficient either.
Thanks for your ideas in advance.
Marius
If i do understand your requirement correctly, this should gives you what you want
SELECT *
FROM
TableA a
OUTER APPLY
(
SELECT TOP 1 *
FROM TableB b
WHERE a.ItemCode = b.ItemCode
AND a.ItemType = b.ItemType
AND b.FundID IN (1, 99)
ORDER BY b.FundID
) b
You can change the query to
AND b.FundID IN (1,99)
or
AND (b.FundID = 1 or b.FundID = 99)
This is the best solution I have received so far. Thanks to #HABO (see the comments section of my question).
Add a column to create a Row_Number() partitioned on ItemType and ItemCode and ordered by FundId, then use only the results with row number 1
For posterity:
-- Sample data.
declare #TableA as Table ( AId Int Identity, ItemCode VarChar(20), ItemType VarChar(20) );
declare #TableB as Table ( BId Int Identity, ItemCode VarChar(20), ItemType VarChar(20), FundId Int );
insert into #TableA ( ItemCode, ItemType ) values
( 'Nemo', 'Fish' ), ( 'Blinky', 'Fish' ), ( 'Muddy Mudskipper', 'Fish' ),
( 'Hammer', 'Tool' ), ( 'Screwdriver', 'Tool' ), ( 'Politician', 'Tool' ),
( 'Grape Nehi', 'Beverage' ), ( 'Screwdriver', 'Beverage' );
insert into #TableB ( ItemCode, ItemType, FundId ) values
( 'Blinky', 'Fish', 1 ), ( 'Muddy Mudskipper', 'Fish', 2 ),
( 'Hammer', 'Tool', 1 ), ( 'Screwdriver', 'Tool', 99 ),
( 'Politician', 'Tool', 1 ), ( 'Politician', 'Tool', 99 ),
( 'Grape Nehi', 'Beverage', 42 ), ( 'Screwdriver', 'Beverage', 1 );
select * from #TableA;
select * from #TableB;
-- Do the deed.
with JoinWithRanking as (
select A.AId, A.ItemCode, A.ItemType, B.BId, B.FundId,
Row_Number() over ( partition by A.ItemCode, A.ItemType order by B.FundId ) as RN
from #TableA as A left outer join
#TableB as B on B.ItemCode = A.ItemCode and B.ItemType = A.ItemType and
B.FundId in ( 1, 99 )
)
select AId, ItemCode, ItemType, BId, FundId
from JoinWithRanking
where RN = 1;

Using table data in SSMS'08 as an '03 access form header and combining several rows into one. Audit table

I have an audit table which displays data as so:
PK FieldName OldValue NewValue UpdateDate
----------------------------------------------------------------------
<orderline=485040867> qtyrec 0 1 2016-05-03 09:09:04.223
<orderline=485040867> statcode RD RC 2016-05-03 09:09:04.223
<orderline=485040867> qty NULL 1 2016-04-28 03:24:57.490
<orderline=485040867> qtyrec NULL 0 2016-04-28 03:24:57.490
<orderline=485040867> statcode NULL NP 2016-04-28 03:24:57.490
<orderline=485040867> batch NULL MRP280416A 2016-04-28 17:01:57.160
<orderline=485040867> statcode NP OR 2016-04-28 17:01:57.160
<orderline=485040867> statcode OR RD 2016-04-29 05:06:29.100
As may hopefully be clear from this, this table logs when various things are updated or inserted for an orderline. Recieved quantity for example. What I would like to do is create a form in access to display all changes which occured at a given date + time on one row for any order.
This would involve using the records in the FieldName column as column headers and my end product would hopefully look something along the lines of
PK qty qtyrec statcode batch Updatedate
orderline = 485040867 1 0 NP 2016-04-28 03:24:57.490
orderline = 485040867 1 0 OR MRP280416A 2016-04-28 17:01:57.160
orderline = 485040867 1 1 RC MRP280416A 2016-05-03 09:09:04.223
I have used your example input for a variable table like this:
DECLARE #t TABLE (
PK NVARCHAR(255),
FieldName NVARCHAR(255),
OldValue NVARCHAR(255),
NewValue NVARCHAR(255),
UpdateDate DATETIME
)
INSERT INTO #t VALUES
('<orderline=485040867>', 'qty', NULL, '1', '2016-04-28 03:24:57.490'),
('<orderline=485040867>', 'qtyrec', NULL, '0', '2016-04-28 03:24:57.490'),
('<orderline=485040867>', 'statcode', NULL, 'NP', '2016-04-28 03:24:57.490'),
('<orderline=485040867>', 'qtyrec', '0', '1', '2016-05-03 09:09:04.223'),
('<orderline=485040867>', 'statcode', 'RD', 'RC', '2016-05-03 09:09:04.223'),
('<orderline=485040867>', 'batch', NULL, 'MRP280416A', '2016-04-28 17:01:57.160'),
('<orderline=485040867>', 'statcode', 'NP', 'OR', '2016-04-28 17:01:57.160'),
('<orderline=485040867>', 'statcode', 'OR', 'RD', '2016-04-29 05:06:29.100');
I think you would want to group by PK and UpdateDate. So I used a subselect for each column like this:
SELECT a.PK
, (SELECT TOP 1 NewValue
FROM #t b
WHERE b.FieldName = 'qty'
AND b.PK = a.PK
AND b.UpdateDate <= a.UpdateDate
ORDER BY b.UpdateDate DESC) AS qty
, (SELECT TOP 1 NewValue
FROM #t b
WHERE b.FieldName = 'qtyrec'
AND b.PK = a.PK
AND b.UpdateDate <= a.UpdateDate
ORDER BY b.UpdateDate DESC) AS qtyrec
, (SELECT TOP 1 NewValue
FROM #t b
WHERE b.FieldName = 'statcode'
AND b.PK = a.PK
AND b.UpdateDate <= a.UpdateDate
ORDER BY b.UpdateDate DESC) AS statcode
, (SELECT TOP 1 NewValue
FROM #t b
WHERE b.FieldName = 'batch'
AND b.PK = a.PK
AND b.UpdateDate <= a.UpdateDate
ORDER BY b.UpdateDate DESC) AS batch
, a.UpdateDate
FROM (
SELECT PK
, UpdateDate
FROM #t
GROUP BY PK, UpdateDate
) a
This is not really fast. You could alternatively use an outer apply like following example
SELECT a.PK
, qty.NewValue AS qty
, qtyrec.NewValue AS qtyrec
, statcode.NewValue AS statcode
, batch.NewValue AS batch
, a.UpdateDate
FROM (
SELECT PK
, UpdateDate
FROM #t
GROUP BY PK, UpdateDate
) a
OUTER APPLY (
SELECT TOP 1 OldValue, NewValue
FROM #t b
WHERE b.FieldName = 'qty'
AND b.PK = a.PK
AND b.UpdateDate <= a.UpdateDate
ORDER BY b.UpdateDate DESC
) qty
OUTER APPLY (
SELECT TOP 1 OldValue, NewValue
FROM #t b
WHERE b.FieldName = 'qtyrec'
AND b.PK = a.PK
AND b.UpdateDate <= a.UpdateDate
ORDER BY b.UpdateDate DESC
) qtyrec
OUTER APPLY (
SELECT TOP 1 OldValue, NewValue
FROM #t b
WHERE b.FieldName = 'statcode'
AND b.PK = a.PK
AND b.UpdateDate <= a.UpdateDate
ORDER BY b.UpdateDate DESC
) statcode
OUTER APPLY (
SELECT TOP 1 OldValue, NewValue
FROM #t b
WHERE b.FieldName = 'batch'
AND b.PK = a.PK
AND b.UpdateDate <= a.UpdateDate
ORDER BY b.UpdateDate DESC
) batch
but there didn't seem to be any difference in runtimes/io, just the query plan is different. I couldn't come up with a good pivot solution, but you could take a look at this https://technet.microsoft.com/en-us/library/ms177410(v=sql.105).aspx

large number of inner joins within the same table

I have a scenario where all the items are in one table named "table". I am doing this for two items:
SELECT I.A + J.A AS A, I.B + J.B AS B, I.C + J.C AS C FROM table AS I
INNER JOIN Table AS J
ON I.StartDate = J.StartDate
AND I.EndDate = J.EndDate
WHERE I.ItemId = 602028
AND J.ItemId = 602029
and I doing this for three items in the table:
SELECT I.A + J.A + K.A AS A, I.B + J.B + K.B AS B, I.C + J.C + K.C AS C FROM table AS I
INNER JOIN Table AS J
ON I.StartDate = J.StartDate
AND I.EndDate = J.EndDate
INNER JOIN Table AS K
ON I.StartDate = K.StartDate
AND I.EndDate = K.EndDate
WHERE I.ItemId = 602028
AND J.ItemId = 602029
AND K.ItemId = 602030
So now you have an idea of what I am trying to do; straightforward. Here is the issue. The number of items to join is supplied at run time, and this number can be large, up to 200. My question is: what is the most efficient way to do this? Currently it's starting to get ugly at more than 5 joins.
Not sure why you want to alias and pivot as I don't think you need it.
Example to try ..
declare #table table (ID int, A int, B int, C int, startdate date, enddate date)
insert #table (ID, A, B, C, startdate, enddate)
select 123, 1, 1, 1, '2014-10-10', '2014-10-10'
union all
select 456,2, 2, 2, '2014-10-10', '2014-10-10'
union all
select 789,3, 3, 3, '2014-10-10', '2014-10-10'
union all
select 111,4, 4, 4, '2014-10-11', '2014-10-11'
union all
select 222,4, 4, 4, '2014-10-11', '2014-10-11'
select sum(A) as A, sum(B) as B, sum(C) as C, startdate, enddate
from #table
where ID in (123,456,789, 111, 222)
group by startdate, enddate
You could try a union on the tables in a derived table and use sum() on the columns for the selected Dates and Item
I.E
SELECT *
FROM I,
(SELECT SUM(A), SUM(B), SUM(C)
FROM (SELECT * FROM I UNION
SELECT * FROM J UNION
SELECT * FROM K) UnT
WHERE UnT.STARTDATE = I.STARTDATE
AND UnT.ENDDATE = I.ENDDATE
AND UnT.ItemId = I.ItemId
AND I.ItemId = 602028
Now you just have to Union the new table in the derived table.
Please try this !
Select sum(a)as A, sum(b) as B,sum(c) as C from
(select itemid,a,b,c from table t
where itemid in (602028,602029,...)
and exists (select 'x' from table t1 where itemid in (602028,602029,...) and t.startdate=t1.startdate and t.enddate=t1.enddate))a

Recursive relationship on a many to many table

I set up a many to many recursive table like this:
CREATE TABLE PROD
(
IDPROD INT NOT NULL PRIMARY KEY,
NAME VARCHAR(3)
);
CREATE TABLE COMP
(
IDPARENT INT REFERENCES PROD(IDPROD),
IDCHILD INT REFERENCES PROD(IDPROD)
);
INSERT INTO PROD (IDPROD, NAME) VALUES (1, 'abc');
INSERT INTO PROD (IDPROD, NAME) VALUES (2, 'def');
INSERT INTO PROD (IDPROD, NAME) VALUES (3, 'ghi');
INSERT INTO PROD (IDPROD, NAME) VALUES (4, 'jkl');
INSERT INTO PROD (IDPROD, NAME) VALUES (5, 'mno');
INSERT INTO COMP (IDPARENT, IDCHILD) VALUES (1, 2);
INSERT INTO COMP (IDPARENT, IDCHILD) VALUES (3, 4);
INSERT INTO COMP (IDPARENT, IDCHILD) VALUES (4, 5);
With a recursivs CTE I can get all the children of a specific node from the second table.
WITH RECURSIVE TEST (IDPARENT, IDCHILD) AS
(SELECT P0.IDPARENT, P0.IDCHILD
FROM COMP AS P0
WHERE P0.IDPARENT = 3
UNION ALL
SELECT P1.IDPARENT, P1.IDCHILD
FROM COMP AS P1, TEST AS T
WHERE T.IDCHILD = P1.IDPARENT)
SELECT * FROM TEST
But I need a query that will give me the entire structure, not just for one node. Like in the classic adjacency list where you get all the root nodes where IDPARENT IS NULL and their children listed below. I use Firebird.
I'm not familiar with Firebird, but this works in SQL Server, so is hopefully similar / enough to get you on track:
WITH TEST (IDRoot, IDPARENT, IDCHILD) AS
(
SELECT P0.IDPROD, C0.IDParent, C0.IDCHILD
FROM PROD AS P0
left outer join COMP C0 on C0.IDParent = P0.IDPROD
WHERE P0.IDProd not in (select IDChild from COMP)
UNION ALL
SELECT T.IDRoot, C1.IDPARENT, C1.IDCHILD
FROM COMP AS C1
inner join TEST AS T on T.IDCHILD = C1.IDPARENT
)
SELECT * FROM TEST
Hope that helps.
SQL Fiddle Version: http://sqlfiddle.com/#!6/22f84/7
Notes
Includ a column to denote the root of the tree as well as parent/child - since there may be multiple trees if we're not specifying a particular root:
WITH TEST (IDRoot, IDPARENT, IDCHILD) AS
Treat any product which is not a child as a ROOT (i.e. first item in a tree).
WHERE P0.IDProd not in (select IDChild from COMP)
EDIT: Answer to comments
Query on any node to see all of its relatives:
The simple way to filter on any node would be to amend the above statement's WHERE P0.IDProd not in (select IDChild from COMP) with WHERE P0.IDProd = IdImInterestedIn. However if you want to use the CTE for a view, then run queries over this static query you could use the code below - you can then filter on IDProd (select * from test where IDProd = IdImInterestedIn) to see that item's ancestors and descendants.
WITH TEST (IDProd, IDRelation, Generation) AS
(
SELECT IDPROD
, IDPROD
, 0
FROM PROD
UNION ALL
SELECT T.IDPROD
, C.IdParent
, T.Generation - 1
FROM TEST AS T
inner join Comp as C
on C.IdChild = T.IDRelation
where t.Generation <= 0
UNION ALL
SELECT T.IDPROD
, C.IdChild
, T.Generation + 1
FROM TEST AS T
inner join Comp as C
on C.IdParent = T.IDRelation
where t.Generation >= 0
)
SELECT *
FROM TEST
order by IDProd, Generation
SQL Fiddle: http://sqlfiddle.com/#!6/22f84/15
See a root node's full tree in a single column
WITH TEST (IDRoot, IDPARENT, IDCHILD, TREE) AS
(
SELECT P0.IDPROD, C0.IDParent, C0.IDCHILD, cast(P0.IDPROD as nvarchar(max)) + coalesce(', ' + cast(C0.IDCHILD as nvarchar(max)),'')
FROM PROD AS P0
left outer join COMP C0 on C0.IDParent = P0.IDPROD
WHERE P0.IDProd not in (select IDChild from COMP)
UNION ALL
SELECT T.IDRoot, C1.IDPARENT, C1.IDCHILD, TREE + coalesce(', ' + cast(C1.IDCHILD as nvarchar(max)),'')
FROM COMP AS C1
inner join TEST AS T on T.IDCHILD = C1.IDPARENT
)
SELECT *
FROM TEST
order by IDRoot
SQL Fiddle: http://sqlfiddle.com/#!6/22f84/19
EDIT: Answer to Additional Comments
with cte (tree_root_no, tree_row_no, relation_sort, relation_chart, Name, id, avoid_circular_ref) as
(
select row_number() over (order by p.idprod)
, 1
, cast(row_number() over (order by p.idprod) as nvarchar(max))
, cast('-' as nvarchar(max))
, p.NAME
, p.IDPROD
, ',' + cast(p.IDPROD as nvarchar(max)) + ','
from PROD p
where p.IDPROD not in (select IDCHILD from COMP) --if it's nothing's child, it's a tree's root
union all
select cte.tree_root_no
, cte.tree_row_no + 1
, cte.relation_sort + cast(row_number() over (order by p.idprod) as nvarchar(max))
, replace(relation_chart,'-','|') + ' -'
, p.NAME
, p.IDPROD
, cte.avoid_circular_ref + cast(p.IDPROD as nvarchar(max)) + ','
from cte
inner join COMP c on c.IDPARENT = cte.id
inner join PROD p on p.IDPROD = c.IDCHILD
where charindex(',' + cast(p.IDPROD as nvarchar(max)) + ',', cte.avoid_circular_ref) = 0
)
select tree_root_no, tree_row_no, relation_sort, relation_chart, id, name
from cte
order by tree_root_no, relation_sort
SQL Fiddle: http://sqlfiddle.com/#!6/4397f/9
Update to show each path
This one's a nasty hack, but the only way I could think of to solve your puzzle; this gives each path through the trees its own number:
;with inner_cte (parent, child, sequence, treePath) as (
select null
, p.IDPROD
, 1
, ',' + CAST(p.idprod as nvarchar(max)) + ','
from #prod p
where IDPROD not in
(
select IDCHILD from #comp
)
union all
select cte.child
, c.IDCHILD
, cte.sequence + 1
, cte.treePath + CAST(c.IDCHILD as nvarchar(max)) + ','
from inner_cte cte
inner join #comp c on c.IDPARENT = cte.child
)
, outer_cte (id, value, pathNo, sequence, parent, treePath) as
(
select icte.child, p.NAME, ROW_NUMBER() over (order by icte.child), icte.sequence, icte.parent, icte.treePath
from inner_cte icte
inner join #prod p on p.IDPROD = icte.child
where icte.child not in (select coalesce(icte2.parent,-1) from inner_cte icte2)
union all
select icte.child, p.NAME, octe.pathNo,icte.sequence, icte.parent, icte.treePath
from outer_cte octe
inner join inner_cte icte on icte.child = octe.parent and CHARINDEX(icte.treePath, octe.treePath) > 0
inner join #prod p on p.IDPROD = icte.child
)
select id, value, pathNo
from outer_cte
order by pathNo, sequence
SQL Fiddle here: http://sqlfiddle.com/#!6/5a16e/1