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
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments