【问题标题】:SQL Server skip duplicate relations (parent child) in recursionSQL Server 在递归中跳过重复关系(父子)
【发布时间】:2018-01-04 22:11:47
【问题描述】:

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

【问题讨论】:

  • 你如何区分 1 的 2 个孩子和 6 的 2 个孩子?
  • 您的数据有缺陷。你根本不能把它像那样放在一起。你怎么知道一组 2,4 属于 1 之下,而另一组属于 6 之下。如果你需要那种关系,你需要重建你的数据结构,因为作为一个标准的父子它是行不通的。
  • 这里有一个根本问题。可以根据不同的方法遍历树。没有真正意义上的“第一”——谁是说哪个节点的出现值得孩子。节点不能“实例化”然后通过它们的祖先来区分,这使得它们不再是节点。
  • 我的数据肯定没问题。在表中,我有记录 1(父)2(子)1(位置)和 6(父)2(子)2(位置)。所有这三个字段在树中都有唯一的位置。但是我没有理由为节点 2 递归地获取所有(可能是 10 级或更多)两次(大写)。

标签: sql-server sql-server-2016


【解决方案1】:

你可以,在 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 是主键。以便其他字段保留在该其他表中。

【讨论】:

  • 但在这种情况下 2 - 4 和 2 - 5 也是重复的。例如,如果节点 2 下方有 10 个级别,则递归必须将所有 10 个级别都向下执行两次。如果可能的话,我想避免这种情况。
  • @Makla 您能否在您的问题中添加预期结果?正如您现在所表达的那样,结果中只有 2-4,任何地方都没有 2-5。
【解决方案2】:

从以下位置更改您的上一个查询:

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。当行匹配时,这将采用最大 id,这通常是您的最新条目,如果您想要最早的条目,只需将 MAX() 更改为 MIN()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-05-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-23
    • 2014-10-24
    • 1970-01-01
    相关资源
    最近更新 更多