SQL递归逻辑
Posted
技术标签:
【中文标题】SQL递归逻辑【英文标题】:SQL recursive logic 【发布时间】:2012-12-12 02:16:02 【问题描述】:我有一种情况,我需要配置现有的客户端数据来解决我们的应用程序没有正确更新表中的 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)
【问题讨论】:
你能解释一下为什么结果是你发布的那个吗?,我似乎找不到ShouldBe
列的规则
当然,如果措辞不当,我们深表歉意。我试图将一个复杂而混乱的问题塞进一个尽可能短的问题中。 “ShouldBe”列反映了“ParentID”列应更新为的内容。在 Parent 表中,第 4 行被 5 替换,然后被 6 替换。7 被 8 替换,然后被 9 替换。第 1 行被 2 替换,并且 3 是父表示例中的唯一行那根本没有被替换。这说明清楚了吗?
是的,确实如此。不过,如果您将该评论编辑到问题中,它可能会有所帮助。
【参考方案1】:
我有一个迭代 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 时它才会停止,这意味着我们无事可做。
在您的示例数据上,上述结果是预期的输出。
请注意,我必须在此处使用临时表而不是表变量,以便在循环中可以访问表。如果您必须使用表变量,则需要做更多的手术。
很明显,如果您有深层替换层次结构,那么您将进行大量更新,这可能是在针对生产数据库执行查询时需要考虑的因素。
【讨论】:
。 .这不是“递归”语句;这是一个迭代语句。看看我的答案和 Lamak 的答案,看看什么是递归 CTE。【参考方案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)
【讨论】:
这也产生了我正在寻找的结果,我也想对此表示赞赏。也感谢您推动更清楚地说明问题!【参考方案3】:以下返回您要查找的内容:
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,因为我们有相同的答案,但你有一个解释:-)。 谢谢,这正是我想要的。我将其标记为答案,因为我也很欣赏这个解释。以上是关于SQL递归逻辑的主要内容,如果未能解决你的问题,请参考以下文章