父子 SQL 递归

Parent Child SQL Recursion

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

如果我有以下 table

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

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

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

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

编辑: 这是一个示例。我使用大括号 { 和 } 作为我的分隔符,我还意识到如果我添加一个 "IsLeaf" 指示符来减少重复,代码会更清晰,因为 leaf-level 记录将包含所有的 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}%'

效率不高,但可以完成工作:

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

table 将是

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

输出将是:

1   2
1   3
5   1
6   1
5   7

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

留作 reader 的练习:这两种方法是否会在 "tree" 中存在循环时终止,例如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;

我试试这个,我的树结构数据来源:

;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