父子 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
我见过类似但不完全相同的请求。
如果我有以下 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