我有一棵树,其中树中的特定节点可以出现在树中的另一个节点中。 (在我的例子中为2):
1
/ \
2 3
/ \ \
4 5 6
\
2
/ \
4 5
通知2重复。首先低于1,低于6。 我的递归是:
with cte (ParentId, ChildId, Field1, Field2) AS (
select BOM.ParentId, BOM.ChildId, BOM.Field1, BOM.Field2
from BillOfMaterials BOM
WHERE ParentId=x
UNION ALL
SELECT BOM.ParentId, BOM.ChildId, BOM.Field1, BOM.Field2 FROM BillOfMaterials BOM
JOIN cte on BOM.ParentId = cte.ChildId
)
select * from cte;
但问题在于结果关系2-4和2-5是重复的(首先来自关系1-2,第二来自关系6-2):
ParentId ChildId OtherFields
1 2
1 3
2 4 /*from 1-2*/
2 5 /*from 1-2*/
3 6
6 2
2 4 /*from 6-2*/
2 5 /*from 6-2*/
有没有办法,跳过访问重复的关系?我没有看到任何逻辑为什么递归会在已经存在于结果中的行上运行。它会更快。这样的事情:
with cte (ParentId, ChildId, Field1, Field2) AS (
select BOM.ParentId, BOM.ChildId, BOM.Field1, BOM.Field2
from BillOfMaterials BOM
WHERE ParentId=x
UNION ALL
SELECT BOM.ParentId, BOM.ChildId, BOM.Field1, BOM.Field2 FROM BillOfMaterials BOM
JOIN cte on BOM.ParentId = cte.ChildId
------> WHERE (select count(*) FROM SoFarCollectedResult WHERE ParentId=BOM.ParentId AND ChildId=BOM.ChildId ) = 0
)
select * from cte;
我找到了this thread,但已经8岁了 我正在使用SQL Server 2016。
如果这是不可能的,那么我的问题是如何从最终结果中删除重复项,但只检查ParentId和ChildId列上的重复项?
编辑:
预期结果是:
ParentId ChildId OtherFields
1 2
1 3
2 4
2 5
3 6
6 2
答案 0 :(得分:0)
更改您的上一个查询:
select * from cte;
要:
select * from cte group by ParentId, ChildId;
这基本上会采用你现在所拥有的,但更进一步,删除已经出现的行,这将解决你的重复问题。请确保此处返回的所有*
都是ParentId
和ChildId
,如果它返回其他列,您需要将它们添加到GROUP BY
或应用某种类型聚合器,以便它仍然可以分组(最大,最小,计数......)。
如果您有更多行无法聚合或分组,您可以这样编写查询:
select * from cte where ID in (select MAX(ID) from cte group by ParentId, ChildId);
ID
将成为cte的主要表ID。如果您希望最早的条目将MAX()
更改为MIN()
,则会在行匹配时采用最大ID,这通常是您的最新条目。
答案 1 :(得分:0)
你可以在SQL上添加2个小技巧。
但是您需要一个带序号的额外Id列 例如,通过身份或日期时间字段显示插入记录的时间 出于简单的原因,就数据库而言,除非您有一个指示该顺序的列,否则在插入记录时没有顺序。
技巧1)仅将CTE记录加入更高的Id。因为如果它们较低,则那些是您不想加入的重复项。
技巧2)使用窗口函数Row_number只获取那些最接近Id的递归的递归
示例:
declare @BillOfMaterials table (Id int identity(1,1) primary key, ParentId int, ChildId int, Field1 varchar(8), Field2 varchar(8));
insert into @BillOfMaterials (ParentId, ChildId, Field1, Field2) values
(1,2,'A','1-2'),
(1,3,'B','1-3'),
(2,4,'C','2-4'), -- from 1-2
(2,5,'D','2-5'), -- from 1-2
(3,6,'E','3-6'),
(6,2,'F','6-2'),
(2,4,'G','2-4'), -- from 6-2
(2,5,'H','2-5'); -- from 6-2
;with cte AS
(
select Id as BaseId, 0 as Level, BOM.*
from @BillOfMaterials BOM
WHERE ParentId in (1)
UNION ALL
SELECT CTE.BaseId, CTE.Level + 1, BOM.*
FROM cte
JOIN @BillOfMaterials BOM on (BOM.ParentId = cte.ChildId and BOM.Id > CTE.Id)
)
select ParentId, ChildId, Field1, Field2
from (
select *
--, row_number() over (partition by BaseId, ParentId, ChildId order by Id) as RNbase
, row_number() over (partition by ParentId, ChildId order by Id) as RN
from cte
) q
where RN = 1
order by ParentId, ChildId;
结果:
ParentId ChildId Field1 Field2
-------- ------- ------ ------
1 2 A 1-2
1 3 B 1-3
2 4 C 2-4
2 5 D 2-5
3 6 E 3-6
6 2 F 6-2
无论如何,作为旁注,通常使用不同的父子关系表 更常见的是,它只是一个具有唯一父子组合的表,这些组合是另一个表的外键,其中Id是主键。这样其他字段就保存在另一个表中。