多对多表上的递归关系

时间:2014-06-15 19:04:20

标签: sql tree firebird

我设置了多个这样的递归表:

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);

使用recursivs CTE,我可以从第二个表中获取特定节点的所有子节点。

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

但我需要一个查询,它将为我提供整个结构,而不仅仅是一个节点。就像在经典的邻接列表中一样,您可以获得IDPARENT为NULL的所有根节点以及下面列出的子节点。我使用Firebird。

1 个答案:

答案 0 :(得分:4)

我对Firebird并不熟悉,但这在SQL Server中有效,所以希望类似/足以让你走上正轨:

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

希望有所帮助。

SQL小提琴版:http://sqlfiddle.com/#!6/22f84/7

备注

包含一列以表示树的根以及父/子 - 因为如果我们没有指定特定的根,可能会有多个树:

WITH TEST (IDRoot, IDPARENT, IDCHILD) AS

将任何非儿童产品视为ROOT(即树中的第一项)。

WHERE P0.IDProd not in (select IDChild from COMP)

编辑:回复评论

查询任何节点以查看其所有亲属:

在任何节点上过滤的简单方法是使用WHERE P0.IDProd not in (select IDChild from COMP)修改上述语句WHERE P0.IDProd = IdImInterestedIn。但是,如果要将CTE用于视图,则可以使用下面的代码对此静态查询运行查询 - 然后可以对IDProdselect * from test where IDProd = IdImInterestedIn)进行过滤,以查看该项目的祖先和后代

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小提琴:http://sqlfiddle.com/#!6/22f84/15

在单个列中查看根节点的完整树

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小提琴:http://sqlfiddle.com/#!6/22f84/19

编辑:回答其他评论

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小提琴:http://sqlfiddle.com/#!6/4397f/9

更新以显示每条路径

这是一个讨厌的黑客,但我能想到的唯一方法来解决你的难题;这为树中的每条路径提供了自己的编号:

;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小提琴:http://sqlfiddle.com/#!6/5a16e/1