SQL递归逻辑

时间:2012-12-26 14:30:09

标签: sql sql-server recursion common-table-expression

我遇到这种情况,我需要配置现有的客户端数据,以解决我们的应用程序未正确更新表中ID的问题。

这是场景。我们有一个父表,可以插入行来有效地替换现有行;替换可以是递归的。我们还有一个子表,它有一个指向父表的字段。在现有数据中,子表可能指向已替换的行,我需要更正它。我不能简单地将每一行更新为替换行,因为 行也可以被替换,我需要反映最新的行。

我试图找到一种方法来编写一个可以为我完成此操作的CTE,但我很难找到一个查找我正在寻找的内容的查询。这是我正在使用的表格的示例; 'ShouldBe'列是我想要的最终更新查询,考虑到某些行的递归替换。

DECLARE @parent TABLE (SampleID int, 
                   SampleIDReplace int,
                   GroupID char(1))

INSERT INTO @parent (SampleID, SampleIDReplace, GroupID)
VALUES (1, -1, 'A'), (2, 1, 'A'), (3, -1, 'A'), 
       (4, -1, 'A'), (5, 4, 'A'), (6, 5, 'A'),
       (7, -1, 'B'), (8, 7, 'B'), (9, 8, 'B')


DECLARE @child TABLE (ChildID int, ParentID int)
INSERT INTO @child (ChildID, ParentID)
VALUES (1, 4), (2, 7), (3, 1), (4, 3)

在应用更新脚本后,子表中的所需结果:

ChildID     ParentID    ParentID_ShouldBe
1           4           6 (4 replaced by 5, 5 replaced by 6)
2           7           9 (7 replaced by 8, 8 replaced by 9)
3           1           2 (1 replaced by 2)
4           3           3 (unchanged, never replaced)

3 个答案:

答案 0 :(得分:4)

以下内容将返回您要查找的内容:

with cte as (
    select sampleid, sampleidreplace, 1 as num
    from @parent
    where sampleidreplace <> -1
    union all
    select p.sampleid, cte.sampleidreplace, cte.num+1
    from @parent p join
         cte
         on p.sampleidreplace = cte.sampleId
)
select c.*, coalesce(p.sampleid, c.parentid)
from @child c left outer join
     (select ROW_NUMBER() over (partition by sampleidreplace order by num desc) as seqnum, *
      from cte
     ) p
     on c.ParentID = p.SampleIDReplace and p.seqnum = 1

递归部分跟踪每个对应关系(4→5,4-> 6)。添加号码是“生成”计数。我们实际上想要上一代。这是通过使用row_number()函数来识别的,按递减顺序按num排序 - 因此p.seqnum = 1

答案 1 :(得分:2)

好的,所以它花了我一段时间,可能有更好的方法,但这里有一个选项。

DECLARE @parent TABLE (SampleID int, 
                   SampleIDReplace int,
                   GroupID char(1))

INSERT INTO @parent (SampleID, SampleIDReplace, GroupID)
VALUES (1, -1, 'A'), (2, 1, 'A'), (3, -1, 'A'), 
       (4, -1, 'A'), (5, 4, 'A'), (6, 5, 'A'),
       (7, -1, 'B'), (8, 7, 'B'), (9, 8, 'B')


DECLARE @child TABLE (ChildID int, ParentID int)
INSERT INTO @child (ChildID, ParentID)
VALUES (1, 4), (2, 7), (3, 1), (4, 3)


;WITH RecursiveParent1 AS
(
    SELECT SampleIDReplace, SampleID, 1 RecursionLevel
    FROM @parent
    WHERE SampleIDReplace != -1
    UNION ALL
    SELECT A.SampleIDReplace, B.SampleID, RecursionLevel + 1
    FROM RecursiveParent1 A
    INNER JOIN @parent B
        ON A.SampleId = B.SampleIDReplace
),RecursiveParent2 AS
(
    SELECT  *, 
            ROW_NUMBER() OVER(PARTITION BY SampleIdReplace ORDER BY RecursionLevel DESC) RN
    FROM RecursiveParent1
)
SELECT A.ChildID, ISNULL(B.ParentID,A.ParentID) ParentID
FROM @child A
LEFT JOIN ( SELECT SampleIDReplace, SampleID ParentID 
            FROM RecursiveParent2
            WHERE RN = 1) B
    ON A.ParentID = B.SampleIDReplace
OPTION(MAXRECURSION 500)

答案 2 :(得分:0)

我有一个迭代的SQL循环,我认为按如下方式排序:

WHILE EXISTS (SELECT * FROM #child C INNER JOIN #parent P ON C.ParentID = P.SampleIDReplace WHERE P.SampleIDReplace > -1)
BEGIN
    UPDATE #child
    SET ParentID = SampleID
    FROM #parent 
    WHERE #child.ParentID = SampleIDReplace
END

基本上,while条件比较子表中父ID列的内容,并查看父表的SampleIDReplace列中是否存在匹配值。如果有,它会获取该记录的SampleID。它只在连接导致每个SampleIDReplace为-1时停止,这意味着我们没有别的事情要做。

在您的样本数据上,上述结果会产生预期的输出。

请注意,我必须在此处使用临时表而不是表变量,以便在循环中访问表。如果你必须使用表变量,则需要进行更多的手术。

显然,如果您有深度替换层次结构,那么您将进行大量更新,这在考虑对生产数据库执行查询时可能需要考虑。