父子SQL递归

时间:2015-06-01 21:12:44

标签: sql sql-server tsql recursion parent-child

我见过类似但不完全相同的请求。

如果我有下表

Parent  Child
1       2
1       3
4       3
5       1
6       1
5       7
8       9

我选择了“1”我会期待所有记录,其中一个是父母或孩子,但也是所有相关的父母和孩子,例如行“5,7”,因为5是“1”的父母

所以1的结果集是

Parent  Child
1       2
1       3
4       3
5       1
6       1
5       7

因此 NOT 包含行

Parent  Child
8       9

这是我到目前为止的最接近的

;WITH LinksDown AS (
   SELECT *
   FROM RecursiveTable
   WHERE Parent = 1
   UNION ALL
   SELECT rt.*
   FROM RecursiveTable rt
   JOIN LinksDown ld on ld.Child = rt.Parent
),

LinksUp AS (
   SELECT *
   FROM RecursiveTable
   WHERE Child = 1
   UNION ALL
   SELECT rt.*
   FROM RecursiveTable rt
   JOIN LinksUp lu on lu.Child = rt.Parent
) 

select distinct *
from LinksDown

Union All 
select distinct * from LinksUp

但是这有以下输出,远不是什么需要

Parent  Child
1       2
1       3
1       2
1       3
5       1
6       1

4 个答案:

答案 0 :(得分:2)

我认为您仍然可以使用CTE执行此操作,作为存储过程的一部分。 (表现会很糟糕,但这应该有效。)

使用递归CTE的常规方法通常会生成3列:ParentID,ChildID,RecursionLevel。

我的建议是再返回一列......一个字符串,它是所有父ID的串联。 (可能有一些分隔符值,如垂直管道。)从那里,您应该能够选择IDString列包含您的ID的每一行。 (在您的情况下,它将是" 1"。)这应该返回您的搜索ID出现在层次结构中某处的每个记录,而不仅仅是父或子。

编辑:以下是一个示例。我使用大括号{和}作为我的分隔符,我也意识到如果我添加一个" IsLeaf"那么代码会更干净。减少重复的指标,因为叶级记录将包含所有祖先的ID ......

DECLARE @MyTable TABLE(P int, C int)  -- Parent & Child

INSERT @MyTable VALUES( 1, 2 );
INSERT @MyTable VALUES( 1, 3 );
INSERT @MyTable VALUES( 3, 4 );
INSERT @MyTable VALUES( 3, 5 );
INSERT @MyTable VALUES( 2, 6 );
INSERT @MyTable VALUES( 5, 7 );
INSERT @MyTable VALUES( 6, 8 );
INSERT @MyTable VALUES( 8, 9 );

-- In order to user a recursive CTE, you need to "know" which records are the 'root' records...
INSERT @MyTable VALUES ( null, 1 );

/*
        9
       /
      8
     /
    6
   / 
  2   
 /     
1   4    Using this example, if the user searched for 1, everything would show up.
 \ /       Searching for 3 would return 1, 3, 4, 5, 7
  3        Searching for 7 would return 1, 3, 5, 7
   \
    5
     \
      7
*/


WITH RecursiveCTE AS (
    SELECT C as ID, 
         0 as Level, 
         CONVERT(varchar(max), '{' + CONVERT(char(1), C) + '}') as IDList,
         CASE WHEN EXISTS (SELECT * FROM @MyTable B Where B.P = 1) THEN 0 ELSE 1 END as IsLeaf
    FROM @MyTable A
    Where A.P IS NULL
  UNION ALL
  SELECT child.C as ID, 
          Level + 1 as Level, 
          IDList + '{' + CONVERT(varchar(max), child.C) + '}' as IDList, 
          CASE WHEN EXISTS (SELECT * FROM @MyTable C Where C.P = child.C) THEN 0 ELSE 1 END as IsLeaf
    FROM RecursiveCTE as parent
    INNER JOIN @MyTable child ON child.P = parent.ID
)
SELECT IDList -- Every ID listed here is a row that you want.
FROM   RecursiveCTE 
WHERE  IsLeaf = 1
AND    IDList LIKE '%{3}%'

答案 1 :(得分:2)

这是两种方法。第一种使用效率非常低的CTE。问题是在递归期间,您无法检查结果集中的所有其他行。虽然您可以构建已贡献给给定行的行的列表,但您无法检查是否已通过其他路径到达该行。第二种方法使用循环一次一步地填充表关系。这是一种比CTE更好的方法。

留给读者的练习:这两种方法是否会在"树"中存在循环时终止,例如1> 2> 3> 1?

-- Sample data.
declare @RecursiveTable as Table ( Parent Int, Child Int );
insert into @RecursiveTable ( Parent, Child ) values
  ( 1, 2 ), ( 1, 3 ),
  ( 4, 3 ),
  ( 5, 1 ),
  ( 6, 1 ),
  ( 5, 7 ),
  ( 8, 9 );
select * from @RecursiveTable;

-- Walk the tree with a recursive CTE.
--   NB: This is woefully inefficient since we cannot promptly detect
--   rows that have already been processed.
declare @Start as Int = 1;
with Pairs as (
  select Parent, Child, Cast( Parent as VarChar(10) ) + '/' + Cast( Child as VarChar(10) ) as Pair
    from @RecursiveTable ),
 Relations as (
  select Parent, Child, Cast( '|' + Pair + '|' as VarChar(1024) ) as Path
    from Pairs
    where Parent = @Start or Child = @Start
  union all
  select P.Parent, P.Child, Cast( R.Path + P.Pair + '|' as VarChar(1024) )
    from Relations as R inner join
      Pairs as P on P.Child = R.Parent or P.Parent = R.Child or
        P.Child = R.Child or P.Parent = R.Parent
    where CharIndex( '|' + P.Pair + '|', R.Path ) = 0
  )
  -- To see how terrible this is, try: select * from Relations
  select distinct Parent, Child
    from Relations
    order by Parent, Child;

-- Try again a loop to add relations to a working table.
declare @Relations as Table ( Parent Int, Child Int );
insert into @Relations
  select Parent, Child
    from @RecursiveTable
    where Parent = @Start or Child = @Start;
while @@RowCount > 0
  insert into @Relations
    select RT.Parent, RT.Child
      from @Relations as R inner join
        @RecursiveTable as RT on RT.Child = R.Child or RT.Parent = R.Parent or
          RT.Child = R.Parent or RT.Parent = R.Child
    except
    select Parent, Child
      from @Relations;
select Parent, Child
  from @Relations
  order by Parent, Child;

答案 2 :(得分:1)

效率不高但它能胜任:

<android.support.design.widget.FloatingActionButton
    android:layout_height="wrap_content"
    android:layout_width="wrap_content"
    app:layout_anchor="@id/appbar"
    app:layout_anchorGravity="bottom|right|end"
    android:src="@drawable/ic_discuss"
    android:layout_margin="@dimen/fab_margin"
    android:clickable="true"/>

表格将是

select * from pc;
declare @t table (cid int);
declare @r int;
insert into @t (cid)values(1); -- this is the "parent"
set @r=@@rowcount;
while @r>0 begin
    insert into @t 
    (cid) select pid from pc where (pid in (select cid from @t) or cid in (select cid from @t) ) and pid not in (select cid from @t)
    union select cid from pc where (pid in (select cid from @t) or cid in (select cid from @t) ) and pid not in (select cid from @t);
    set @r = @@ROWCOUNT;
end;

select * from pc where pid in (select cid from @t) or cid in (select cid from @t);

输出将是:

1   2
1   3
4   3
5   1
6   1
5   7
8   9

答案 3 :(得分:0)

///我尝试使用我的树形结构数据源:|

;WITH items AS (
  SELECT Cg_Code, Cg_Name
  , 0 AS Level
  , CAST(Cg_Name AS VARCHAR(255)) AS Path
  FROM GroupsCustomer WHERE Cg_Relation =0
  UNION ALL
  SELECT i.Cg_Code, i.Cg_Name
  , Level + 1
  , CAST(Path + '/' + CAST(i.Cg_Name AS VARCHAR(255)) AS VARCHAR(255)) AS Path
  FROM GroupsCustomer i
  INNER JOIN items itms ON itms.Cg_Code = i.Cg_Relation
  )

SELECT * FROM items ORDER BY Path