SQL Server在递归

时间:2017-07-28 18:38:10

标签: sql-server sql-server-2016

我有一棵树,其中树中的特定节点可以出现在树中的另一个节点中。 (在我的例子中为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

2 个答案:

答案 0 :(得分:0)

更改您的上一个查询:

select * from cte;

要:

select * from cte group by ParentId, ChildId;

这基本上会采用你现在所拥有的,但更进一步,删除已经出现的行,这将解决你的重复问题。请确保此处返回的所有*都是ParentIdChildId,如果它返回其他列,您需要将它们添加到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是主键。这样其他字段就保存在另一个表中。